Hinweis: Obwohl JavaScript für diese Website nicht unbedingt erforderlich ist, werden Ihre Interaktionsmöglichkeiten mit den Inhalten eingeschränkt sein. Bitte aktivieren Sie JavaScript für das volle Erlebnis.

Standard Exception Classes in Python 1.5

Standard Exception Classes in Python 1.5

(aktualisiert für Python 1.5.2 -baw)

Benutzerdefinierte Python-Ausnahmen können entweder Strings oder Python-Klassen sein. Da Klassen viele schöne Eigenschaften haben, wenn sie als Ausnahmen verwendet werden, ist es wünschenswert, zu einer Situation zu migrieren, in der Klassen ausschließlich verwendet werden. Vor Python 1.5 Alpha 4 wurden die Standardausnahmen von Python (IOError, TypeError usw.) als Strings definiert. Die Umwandlung dieser in Klassen stellte einige besonders unangenehme Probleme mit der Abwärtskompatibilität dar.

In Python-Versionen 1.5 und höher sind die Standardausnahmen Python-Klassen, und es wurden einige neue Standardausnahmen hinzugefügt. Die veraltete AccessError-Ausnahme wurde gelöscht. Da es möglich (wenn auch unwahrscheinlich) ist, dass diese Änderung bestehenden Code gebrochen hat, kann der Python-Interpreter mit der Kommandozeilenoption -X aufgerufen werden, um diese Funktion zu deaktivieren und wie bisher String-Ausnahmen zu verwenden. Diese Option ist eine vorübergehende Maßnahme - schließlich werden die String-basierten Standardausnahmen vollständig aus der Sprache entfernt. Es wurde noch nicht entschieden, ob benutzerdefinierte String-Ausnahmen in Python 2.0 zugelassen werden.

Die Standard-Ausnahmehierarchie

Betrachten Sie die Standard-Ausnahmehierarchie. Sie ist im neuen Standardbibliotheksmodul exceptions.py definiert. Ausnahmen, die seit Python 1.5 neu sind, sind mit (*) gekennzeichnet.

Exception(*)
 |
 +-- SystemExit
 +-- StandardError(*)
      |
      +-- KeyboardInterrupt
      +-- ImportError
      +-- EnvironmentError(*)
      |    |
      |    +-- IOError
      |    +-- OSError(*)
      |
      +-- EOFError
      +-- RuntimeError
      |    |
      |    +-- NotImplementedError(*)
      |
      +-- NameError
      +-- AttributeError
      +-- SyntaxError
      +-- TypeError
      +-- AssertionError
      +-- LookupError(*)
      |    |
      |    +-- IndexError
      |    +-- KeyError
      |
      +-- ArithmeticError(*)
      |    |
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      |    +-- FloatingPointError
      |
      +-- ValueError
      +-- SystemError
      +-- MemoryError

Die Stammklasse für alle Ausnahmen ist die neue Ausnahme Exception. Davon werden zwei zusätzliche Klassen abgeleitet: StandardError, die Stammklasse für alle Standardausnahmen, und SystemExit. Es wird empfohlen, dass benutzerdefinierte Ausnahmen im neuen Code von Exception abgeleitet werden, obwohl dies aus Gründen der Abwärtskompatibilität nicht erforderlich ist. Schließlich wird diese Regel verschärft.

SystemExit wird von Exception abgeleitet, weil es zwar eine Ausnahme ist, aber kein Fehler.

Die meisten Standardausnahmen sind direkte Nachkommen von StandardError. Einige verwandte Ausnahmen werden mithilfe einer Zwischenklasse, die von StandardError abgeleitet ist, zusammengefasst; dies ermöglicht es, mehrere verschiedene Ausnahmen in einer einzigen except-Klausel zu behandeln, ohne die Tupelschreibweise zu verwenden.

Wir haben versucht, weitere Gruppen verwandter Ausnahmen einzuführen, konnten uns aber nicht auf die beste Gruppierung einigen. In einer so dynamischen Sprache wie Python ist es schwer zu sagen, ob TypeError ein "Programmfehler", ein "Laufzeitfehler" oder ein "Umgebungsfehler" ist, also haben wir es unentschieden gelassen. Man könnte argumentieren, dass NameError und AttributeError von LookupError abgeleitet werden sollten, aber das ist fraglich und hängt vollständig von der Anwendung ab.

Definitionen von Ausnahme-Klassen

Die Python-Klassendefinitionen für die Standardausnahmen werden aus dem Standardmodul "exceptions" importiert. Sie können diese Datei nicht ändern und denken, dass die Änderungen automatisch in den Standardausnahmen angezeigt werden; das eingebaute Modul erwartet die aktuelle Hierarchie, wie sie in exceptions.py definiert ist.

Details zu den Standard-Ausnahmeklassen finden Sie im Python-Bibliotheken-Referenzhandbuch im Eintrag für das Modul exceptions.

Änderungen an raise

Die raise-Anweisung wurde erweitert, um das Auslösen einer Klassen-Ausnahme ohne explizite Instanziierung zu ermöglichen. Die folgenden Formen, die als "Kompatibilitätsformen" der raise-Anweisung bezeichnet werden, sind zulässig

  • raise exception
  • raise exception, argument
  • raise exception, (argument, argument, ...)

Wenn exception eine Klasse ist, sind diese äquivalent zu den folgenden Formen

  • raise exception()
  • raise exception(argument)
  • raise exception(argument, argument, ...)

Beachten Sie, dass dies alles Beispiele für die Form sind

  • raise instance

die an sich eine Abkürzung ist für

  • raise class, instance

wobei class die Klasse ist, zu der instance gehört. In Python 1.4 waren nur die Formen

  • raise class, instance und
  • raise instance

zugelassen; in Python 1.5 (ab 1.5a1) wurden die Formen

  • raise class und
  • raise class, argument(s)

hinzugefügt. Die zulässigen Formen für String-Ausnahmen bleiben unverändert.

Aus verschiedenen Gründen ist die Übergabe von None als zweites Argument an raise gleichbedeutend mit dem Weglassen. Insbesondere die Anweisung

  • raise class, None

ist äquivalent zu

  • raise class()

und **nicht** zu

  • raise class(None)

Ebenso ist die Anweisung

  • raise class, value

wobei value zufällig ein Tupel ist, gleichbedeutend mit der Übergabe der Elemente des Tupels als einzelne Argumente an den Klassenkonstruktor, anstatt value als einzelnes Argument zu übergeben (und ein leeres Tupel ruft den Konstruktor ohne Argumente auf). Dies macht einen Unterschied, da es einen Unterschied zwischen f(a, b) und f((a, b)) gibt.

Dies sind alles Kompromisse - sie funktionieren gut mit der Art von Argumenten, die die Standardausnahmen typischerweise annehmen (wie ein einfacher String). Zur Klarheit im neuen Code wird die Form

  • raise class(argument, ...)

empfohlen (d. h. ein expliziter Aufruf des Konstruktors).

Wie hilft das?

Die Motivation für die Einführung der Kompatibilitätsformen war die Ermöglichung der Abwärtskompatibilität mit altem Code, der eine Standardausnahme ausgelöst hat. Zum Beispiel könnte ein __getattr__-Hook die Anweisung aufrufen

  • raise AttributeError, attrname

wenn das gewünschte Attribut nicht definiert ist.

Unter Verwendung der neuen Klassen-Ausnahmen wäre die richtige Ausnahme, die ausgelöst werden soll, AttributeError(attrname); die Kompatibilitätsformen stellen sicher, dass der alte Code nicht bricht. (Tatsächlich muss neuer Code, der mit der -X-Option kompatibel sein möchte, die Kompatibilitätsformen verwenden, aber das wird dringend abgeraten.)

Änderungen an except

Es wurden keine benutzerfreundlichen Änderungen an der except-Klausel der try-Anweisung vorgenommen.

Intern hat sich viel verändert. Zum Beispiel werden Klassen-Ausnahmen, die von C ausgelöst werden, instanziiert, wenn sie gefangen werden, nicht wenn sie ausgelöst werden. Dies ist ein Performance-Hack, damit Ausnahmen, die vollständig in C ausgelöst und gefangen werden, niemals die Kosten der Instanziierung tragen. Zum Beispiel löst die Iteration durch eine Liste in einer for-Anweisung am Ende der Liste ein IndexError durch das Listenobjekt aus, aber die Ausnahme wird in C gefangen und daher nie instanziiert.

Was könnte brechen?

Der neue Entwurf tut sein Bestes, um alten Code nicht zu brechen, aber es gibt einige Fälle, in denen es sich nicht gelohnt hat, die neuen Semantiken zu kompromittieren, um das Brechen von Code zu vermeiden. Mit anderen Worten, einige alte Codes könnten brechen. Deshalb gibt es den -X-Schalter; dies sollte jedoch keine Entschuldigung dafür sein, Ihren Code nicht zu reparieren.

Es gibt zwei Arten von Brüchen: Manchmal gibt Code leicht komische Fehlermeldungen aus, wenn er eine Klassen-Ausnahme fängt, aber eine String-Ausnahme erwartet. Und manchmal, aber viel seltener, stürzt Code tatsächlich ab oder verhält sich bei der Fehlerbehandlung falsch.

Nicht-fatale Brüche

Ein Beispiel für die erste Art von Bruch ist Code, der versucht, den Namen der Ausnahme auszugeben, z.B.

try:
    1/0
except:
    print "Sorry:", sys.exc_type, ":", sys.exc_value
Mit String-basierten Ausnahmen würde dies etwa Folgendes ausgeben
Sorry: ZeroDivisionError : integer division or modulo
Mit klassenbasierten Ausnahmen wird dies ausgeben
Sorry: exceptions.ZeroDivisionError : integer division or modulo
Das komische exceptions.ZeroDivisionError tritt auf, weil, wenn ein Ausnahmetyp eine Klasse ist, er als modulname.klassenname ausgegeben wird. Dies wird intern von Python behandelt.

Fatale Brüche

Ernster ist das Brechen von Fehlerbehandlungscode. Dies geschieht normalerweise, weil der Fehlerbehandlungscode erwartet, dass die Ausnahme oder der Wert, der mit der Ausnahme verbunden ist, einen bestimmten Typ hat (normalerweise String oder Tupel). Mit dem neuen Schema ist der Typ eine Klasse und der Wert eine Klasseninstanz. Zum Beispiel wird der folgende Code brechen

try:
    raise Exception()
except:
    print "Sorry:", sys.exc_type + ":", sys.exc_value
weil er versucht, den Ausnahmetyp (ein Klassenobjekt) mit einem String zu verketten. Eine Korrektur (auch für das vorherige Beispiel) wäre zu schreiben
try:
    raise Exception()
except:
    etype = sys.exc_type       # Save it; try-except overwrites it!
    try:
        ename = etype.__name__ # Get class name if it is a class
    except AttributeError:
        ename = etype
    print "Sorry:", str(ename) + ":", sys.exc_value
Beachten Sie, wie dieses Beispiel eine explizite Typenprüfung vermeidet! Stattdessen fängt es einfach die (neue) Ausnahme, die ausgelöst wird, wenn das __name__-Attribut nicht gefunden wird. Nur um absolut sicher zu sein, dass wir einen String verketten, wird die eingebaute Funktion str() angewendet.

Ein weiteres Beispiel betrifft Code, der zu viel über den Typ des mit der Ausnahme verbundenen Werts annimmt. Zum Beispiel

try:
    open('file-doesnt-exist')
except IOError, v:
    if type(v) == type(()) and len(v) == 2:
        (code, message) = v
    else:
        code = 0
        message = v
    print "I/O Error: " + message + " (" + str(code) + ")"
    print
Dieser Code versteht, dass IOError oft mit einem Tupel der Form (errorcode, message) und manchmal nur mit einem String ausgelöst wird. Da er jedoch explizit auf die Tupel-Eigenschaft des Werts testet, wird er abstürzen, wenn der Wert eine Instanz ist!

Auch hier ist die Abhilfe, einfach den Tupel-Unpack zu versuchen, und wenn er fehlschlägt, die Fallback-Strategie zu verwenden

try:
    open('file-doesnt-exist')
except IOError, v:
    try:
        (code, message) = v
    except:
        code = 0
        message = v
    print "I/O Error: " + str(message) + " (" + str(code) + ")"
    print
Dies funktioniert, weil die Tupel-Unpack-Semantik gelockert wurde, um mit jeder Sequenz auf der rechten Seite zu arbeiten (siehe Abschnitt über Sequenz-Unpacking unten), und die Standard-Ausnahmeklassen wie eine Sequenz (dank ihrer __getitem__-Methode, siehe oben) zugegriffen werden können.

Beachten Sie, dass die zweite try-except-Anweisung die zu fangende Ausnahme nicht angibt – dies liegt daran, dass bei String-Ausnahmen die ausgelöste Ausnahme "TypeError: unpack non-tuple" lautet, während bei Klassen-Ausnahmen "ValueError: unpack sequence of wrong size" lautet. Dies liegt daran, dass ein String eine Sequenz ist; wir müssen davon ausgehen, dass Fehlermeldungen immer mehr als zwei Zeichen lang sind!

(Ein alternativer Ansatz wäre die Verwendung von try-except, um auf die Anwesenheit des errno-Attributs zu testen; in Zukunft wäre dies sinnvoll, aber derzeit würde dies noch mehr Code erfordern, um mit String-Ausnahmen kompatibel zu sein.)

Änderungen an der C-API

XXX Wird im Detail beschrieben

int PyErr_ExceptionMatches(PyObject *);
int PyErr_GivenExceptionMatches(PyObject *, PyObject *);
void PyErr_NormalizeException(PyObject**, PyObject**, PyObject**);

PyErr_ExceptionMatches(exception) sollte gegenüber PyErr_Occurred()==exception bevorzugt werden, da letzteres ein falsches Ergebnis zurückgibt, wenn die ausgelöste Ausnahme eine Klasse ist, die von der getesteten Ausnahme abgeleitet ist.

PyErr_GivenExceptionMatches(raised_exception, exception) führt denselben Test wie PyErr_ExceptionMatches() durch, erlaubt Ihnen jedoch, die ausgelöste Ausnahme explizit zu übergeben.

PyErr_NormalizeException() ist hauptsächlich für den internen Gebrauch bestimmt.

Andere Änderungen

Einige Änderungen an der Sprache wurden im Rahmen desselben Projekts vorgenommen.

Neue eingebaute Funktionen

Zwei neue intrinsische Funktionen für Klassen-Tests wurden eingeführt (da die Funktionalität in der C-API implementiert werden musste, gab es keinen Grund, sie Python-Programmierern nicht zugänglich zu machen).

issubclass(D, C) gibt wahr zurück, wenn die Klasse D direkt oder indirekt von der Klasse C abgeleitet ist. issubclass(C, C) gibt immer wahr zurück. Beide Argumente müssen Klassenobjekte sein.

isinstance(x, C) gibt wahr zurück, wenn x eine Instanz von C oder einer (direkten oder indirekten) Unterklasse von C ist. Das erste Argument kann jeden Typ haben; wenn x keine Instanz einer Klasse ist, gibt isinstance(x, C) immer falsch zurück. Das zweite Argument muss ein Klassenobjekt sein.

Sequenz-Unpacking

Frühere Python-Versionen erforderten eine exakte Typübereinstimmung zwischen der linken und der rechten Seite von "Unpacking"-Zuweisungen, z.B.

(a, b, c) = x
erfordert, dass x ein Tupel mit drei Elementen ist, während
[a, b, c] = x
erfordert, dass x eine Liste mit drei Elementen ist.

Im Rahmen desselben Projekts kann die rechte Seite beider Anweisungen eine beliebige Sequenz mit genau drei Elementen sein. Dies ermöglicht die Extraktion von z. B. den errno- und strerror-Werten aus einer IOError-Ausnahme auf abwärtskompatible Weise

try:
    f = open(filename, mode)
except IOError, what:
    (errno, strerror) = what
    print "Error number", errno, "(%s)" % strerror

Der gleiche Ansatz funktioniert auch für die SyntaxError-Ausnahme, mit dem Vorbehalt, dass der Info-Teil nicht immer vorhanden ist

try:
    c = compile(source, filename, "exec")
except SyntaxError, what:
    try:
        message, info = what
    except:
        message, info = what, None
    if info:
        "...print source code info..."
    print "SyntaxError:", msg