Exceptions

In diesem Abschnitt geht es um Ausnahmen (englisch exceptions), d.h. um Sprachfunktionen, die speziell ungewöhnliche Umstände während der Ausführung eines Programms behandeln. Die häufigste Ausnahme ist die Behandlung von Fehlern, aber sie können auch für viele andere Zwecke effektiv eingesetzt werden. Python bietet einen umfassenden Satz von Ausnahmen, und ihr könnt neue Ausnahmen für eure eigenen Zwecke definieren.

Eine Exception ist ein Objekt, das automatisch von Python-Funktionen mit einer raise-Anweisung erzeugt wird, z.B. mit:

11        if line == "":
12            raise EmptyFileError(f"{file} is empty")

Die raise-Anweisung veranlasst die Ausführung des Python-Programms auf eine andere Art und Weise, als üblicherweise vorgesehen: Die aktuelle Aufrufkette wird nach einem Handler durchsucht, der die erzeugte Ausnahme behandeln kann. Wenn ein solcher Handler gefunden wird, wird er aufgerufen und kann auf das Ausnahmeobjekt zugreifen, um weitere Informationen zu erhalten, wie in unserem EmptyFileError-Beispiel:

1class EmptyFileError(Exception):
2    pass

Dies definiert euren eigenen Ausnahmetyp, der vom Basistyp Exception erbt.

Eine Übersicht über die Klassenhierarchie eingebauter Exceptions erhaltet ihr unter Exception hierarchy in der Python-Dokumentation. Jeder Ausnahmetyp ist eine Python-Klasse, die von ihrem übergeordneten Exception-Typ erbt. So ist z.B. ein ZeroDivisionError durch Vererbung auch ein ArithmeticError, eine Exception und auch eine BaseException. Diese Hierarchie ist gewollt: Die meisten Ausnahmen erben von Exception, und es wird dringend empfohlen, dass alle benutzerdefinierten Ausnahmen auch die Unterklasse von Exception und nicht von BaseException bilden:

Es ist möglich, verschiedene Arten von Ausnahmen zu erzeugen, um die tatsächliche Ursache des gemeldeten Fehlers oder außergewöhnlichen Umstandes zu reflektieren.

 8    try:
 9        f = open(file, "r")
10        line = f.readline()
11        if line == "":
12            raise EmptyFileError(f"{file} is empty")
13    except OSError as error:
14        print(f"Cannot open file {file}: {error.strerror}")
15    except EmptyFileError as error:
16        print(error)

Wenn während der Ausführung von open() im try-Block ein OSError oder ein EmptyFileError auftritt, wird der jeweils zugehörige except-Block ausgeführt.

Wird kein geeigneter Exception-Handler gefunden, bricht das Programm mit einer Fehlermeldung ab. Daher ergänzen wir unsere try-except-Anweisungen um else und finally:

17    else:
18        print(f"{file}: {f.readline()}")
19    finally:
20        print("File", file, "processed")
21        f.close()

Nun können wir noch eine Liste unterschiedlicher Datei-Arten definieren, sodass unser vollständiger Code folgendermaßen aussieht:

 1class EmptyFileError(Exception):
 2    pass
 3
 4
 5filenames = ["myFile1.py", "nonExistent.py", "emptyFile.py", "myFile2.py"]
 6
 7for file in filenames:
 8    try:
 9        f = open(file, "r")
10        line = f.readline()
11        if line == "":
12            raise EmptyFileError(f"{file} is empty")
13    except OSError as error:
14        print(f"Cannot open file {file}: {error.strerror}")
15    except EmptyFileError as error:
16        print(error)
17    else:
18        print(f"{file}: {f.readline()}")
19    finally:
20        print("File", file, "processed")
21        f.close()
Zeile 7

Wenn während der Ausführung der Anweisungen im try-Block ein OSError oder EmptyFileError auftritt, wird der zugehörige except-Block ausgeführt.

Zeile 9

Hier könnte ein OSError ausgelöst werden.

Zeile 12

Hier löst ihr den EmptyFileError aus.

Zeile 17

Die else-Klausel ist optional; sie wird ausgeführt, wenn im try-Block keine Ausnahme auftritt.

Zeile 19

Die finally-Klausel ist ebenfalls optional und wird am Ende des Blocks ausgeführt, unabhängig davon, ob eine Ausnahme ausgelöst wurde oder nicht.

Bemerkung

Die Art und Weise, wie Python Fehlersituationen im Allgemeinen behandelt, unterscheidet sich von manch anderen Sprachen, z.B. Java. Diese Sprachen prüfen mögliche Fehler so weit wie möglich, bevor sie auftreten, da die Behandlung von Exceptions nach ihrem Auftreten kostspielig ist. Dies wird manchmal als LBYL-Ansatz bezeichnet.

Bei Python hingegen verlässt man sich eher auf Exceptions, um Fehler zu behandeln, nachdem sie aufgetreten sind. Obwohl dieses Vertrauen riskant erscheinen mag, ist der Code weniger schwerfällig und leichter zu lesen, wenn Exceptions richtig eingesetzt werden, und Fehler werden nur dann behandelt, wenn sie auftreten. Diese pythonische Herangehensweise zur Behandlung von Fehlern wird oft als EAFP beschrieben.

Checks

  • Schreibt Code, der zwei Zahlen erhält und die erste Zahl durch die zweite dividiert. Prüft, ob der ZeroDivisionError auftritt, wenn die zweite Zahl 0 ist, und fangt diese ab.

  • Wenn MyError von Exception erbt, was ist dann der Unterschied zwischen except Exception as e und except MyError as e?

  • Schreibt ein einfaches Programm, das eine Zahl erhält und dann die Anweisung assert() verwendet, um eine Exception auszulösen, wenn die Zahl 0 ist.

  • Schreibt eine benutzerdefinierte Ausnahme Outliers, die eine Exception auslöst, wenn die Variable x größer oder kleiner als 3 ist?

  • Handelt es sich bei der Überprüfung, ob ein Objekt eine Liste ist (Check: list) um eine Programmierung im Stil von LBYL oder EAFP?