5. Die Ausnahmen
Wir wenden uns nun den Ausnahmen zu.

5.1. Skript [exceptions_01]
Das erste Skript veranschaulicht die Notwendigkeit, Ausnahmen zu behandeln.
Wir lösen absichtlich einen Fehler aus, um zu sehen, was passiert (exceptions-01.py):
Zeile 4 gibt uns:
- den Typ der Ausnahme: ZeroDivisionError;
- die zugehörige Fehlermeldung: „division by zero“. Sie ist auf Englisch. Das möchte man vielleicht ändern.
Eine wichtige Regel lautet, dass man alles tun muss, um vom Python-Interpreter ausgelöste Ausnahmen zu vermeiden. Wir müssen die Fehler selbst behandeln.
Die Syntax für die Ausnahmebehandlung lautet wie folgt:
try:
actions susceptibles de lancer une exception
except (Ex1, Ex2…) as ex:
actions de gestion de l'exception [ex]
except (Ex11, Ex12…) as ex:
actions de gestion de l'exception [ex]
finally:
actions toujours exécutées qu'il y ait exception ou non
Im **try**-Block wird die Ausführung der Aktionen unterbrochen, sobald eine Ausnahme (ein Fehler) auftritt. In diesem Fall wird die Ausführung mit den Aktionen einer der except-Klauseln fortgesetzt:
- Zeile 3: Wenn die aufgetretene Ausnahme [ex] zu einem Typ gehört, der im Tupel (Ex1, Ex2…) enthalten ist oder von einem dieser Typen abgeleitet ist, werden die Aktionen in Zeile 4 ausgeführt;
- Zeile 5: Wenn die Ausnahme nicht von Zeile 3 abgefangen wurde und eine weitere Klausel [except] existiert, läuft derselbe Prozess ab. Usw.;
- Es kann so viele [except]-Klauseln geben, wie nötig sind, um die verschiedenen Ausnahmetypen zu behandeln, die im [try] auftreten können;
- Wurde die Ausnahme von keiner der Klauseln [except] behandelt, wird sie an den aufrufenden Code weitergeleitet. Befindet sich dieser selbst in einer try/except-Struktur, wird die Ausnahme erneut behandelt; andernfalls wird sie weiter die Kette der aufgerufenen Methoden hinaufgeleitet. Als letztes gelangt sie zum Python-Interpreter. Dieser bricht daraufhin das ausgeführte Programm ab und zeigt eine Fehlermeldung an, wie sie im vorherigen Beispiel gezeigt wurde. Die Regel lautet daher, dass das Hauptprogramm alle Ausnahmen abfangen muss, die von den aufgerufenen Methoden weitergeleitet werden können;
- Zeile 7: Die Anweisung [finally] wird immer ausgeführt, unabhängig davon, ob eine Ausnahme aufgetreten ist (Fortsetzung des „except“-Blocks) oder nicht (Fortsetzung des „try“-Blocks). Dies gilt auch dann, wenn eine Ausnahme aufgetreten ist und nicht abgefangen wurde. In diesem Fall wird die Klausel [finally] ausgeführt, bevor die Ausnahme an den aufrufenden Code weitergeleitet wird;
Eine Ausnahme enthält Informationen über den aufgetretenen Fehler. Diese können mit der folgenden Syntax abgerufen werden:
except MyException as exception:
[exception] ist die aufgetretene Ausnahme. [exception.args] stellt das Tupel der Ausnahmeparameter dar.
Um eine Ausnahme auszulösen, verwendet man die Syntax
raise MyException(param1, param2…)
wobei MyException in den meisten Fällen eine von der Klasse BaseException abgeleitete Klasse ist. Die an den Konstruktor der Klasse übergebenen Parameter stehen in der „except“-Klausel von Ausnahmebehandlungsstrukturen mit der Syntax [ex.args] zur Verfügung, wenn [ex] die von der Klausel [except] abgefangene Ausnahme ist.
Diese Konzepte werden durch das folgende Skript veranschaulicht.
5.2. Skript [exceptions_02]
Das folgende Skript behandelt Fehler explizit:
# Ausnahmebehandlung
essai = 0
# Ein Fehler wird ausgelöst und behandelt
x = 2
try:
x = 4 / 0
except ZeroDivisionError as erreur:
# Der Fehler ist die abgefangene Ausnahme
print(f"essai n° {essai} : {erreur}")
# Der Wert von x hat sich nicht geändert
print(f"x={x}")
# Man beginnt von vorne
essai += 1
try:
x = 4 / 0
except BaseException as erreur:
# Die allgemeinste Ausnahme wird abgefangen
# Der Fehler ist die abgefangene Ausnahme
print(f"essai n° {essai} : {erreur}")
# Es können verschiedene Arten von Ausnahmen abgefangen werden
# Die Ausführung wird bei der ersten Instanz von [except] angehalten, die die Ausnahme verarbeiten kann
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# Diese Ausnahme tritt hier nicht auf
print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
# Die allgemeinste Ausnahme wird abgefangen
print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
# Ein bestimmter Typ wird abgefangen
print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
# Wir versuchen es erneut und ändern die Reihenfolge
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# Diese Ausnahme tritt hier nicht auf
print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
# Man fängt einen bestimmten Typ ab
print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
# Die allgemeinste Ausnahme wird abgefangen
print(f"essai n° {essai} : (Exception) {erreur}")
# eine „except“-Klausel ohne Argumente
essai += 1
try:
x = 4 / 0
except:
# Die Art der Ausnahme spielt keine Rolle
print(f"essai n° {essai} : il y a eu un problème")
# ein anderer Ausnahmetyp
essai += 1
try:
# x kann nicht in eine Ganzzahl konvertiert werden
x = int("x")
except ValueError as erreur:
# „Fehler“ ist die abgefangene Ausnahme
print(f"essai n° {essai} : {erreur}")
# Eine Ausnahme enthält Informationen in einem Tupel, auf das das Programm zugreifen kann
essai += 1
try:
x = int("x")
except ValueError as erreur:
# „Fehler“ ist die abgefangene Ausnahme
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# Ausnahmen können ausgelöst werden
essai += 1
try:
raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
# Der Fehler ist die abgefangene Ausnahme
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# Man kann eigene Ausnahmen erstellen
# sie müssen von der Klasse [BaseException] abgeleitet sein
class MyError(BaseException):
pass
# Die Ausnahme MyError wird ausgelöst
essai += 1
try:
raise MyError("info1", "info2", "info3")
except MyError as erreur:
# Der Fehler ist die abgefangene Ausnahme
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# Die Ausnahme MyError wird mit einer Fehlermeldung ausgelöst
essai += 1
try:
raise MyError("mon msg d'erreur")
except MyError as erreur:
# Der Fehler ist die abgefangene Ausnahme
print(f"essai n° {essai} : {erreur.args[0]}")
# Die finally-Klausel wird immer ausgeführt
# unabhängig davon, ob eine Ausnahme vorliegt oder nicht
essai += 1
x = None
try:
x = 1
except:
# Ausnahme
print(f"essai n° {essai} : exception")
finally:
# wird in jedem Fall ausgeführt
print(f"essai n° {essai} : finally x={x}")
essai += 1
x = None
try:
x = 2 / 0
except:
# Ausnahme
print(f"essai n° {essai} : exception")
finally:
# wird in jedem Fall ausgeführt
print(f"essai n° {essai} : finally x={x}")
# Es ist nicht zwingend erforderlich, eine Klausel einzufügen [except]
essai += 1
try:
# löst einen Fehler aus
x = 4 / 0
finally:
# wird in jedem Fall ausgeführt
print(f"essai n° {essai} : finally x={x}")
Hinweise:
- Zeilen 4–12: Eine Division durch Null wird behandelt;
- Zeile 8: Die aufgetretene Ausnahme wird abgefangen und angezeigt;
- Zeile 12: Aufgrund der aufgetretenen Ausnahme hat x in Zeile 7 keinen Wert erhalten und hat daher seinen Wert nicht geändert;
- Zeilen 14–21: Wir wiederholen den Vorgang, fangen dabei jedoch eine Ausnahme höherer Ebene vom Typ BaseException ab. Da die Ausnahme ZeroDivisionError von der Klasse BaseException abgeleitet ist, wird sie durch die Klausel except abgefangen;
- Zeilen 23–36: Es werden mehrere Klauseln vom Typ except eingefügt, um verschiedene Ausnahmetypen zu behandeln. Es wird entweder eine einzige Klausel vom Typ except ausgeführt oder gar keine, wenn die Ausnahme keine der „except“-Klauseln erfüllt;
- Zeilen 38–50: Wir wiederholen denselben Vorgang, ändern dabei jedoch die Reihenfolge der Klauseln [except], um deren Funktion zu veranschaulichen;
- Zeilen 52–58: Die Klausel except kann ohne Argumente auskommen. In diesem Fall stoppt sie alle Ausnahmen;
- Zeilen 60–67: Einführung der Ausnahme ValueError;
- Zeilen 69–75: Die von der Ausnahme übertragenen Informationen werden abgerufen;
- Zeilen 77–83: Hier wird beschrieben, wie eine Ausnahme ausgelöst (raise) wird;
- Zeilen 86–106: veranschaulichen die Verwendung einer proprietären Ausnahmeklasse MyError. Die Klasse MyError leitet lediglich von der Basisklasse BaseException ab. Sie fügt ihrer Basisklasse nichts hinzu. Nun kann sie jedoch explizit in den „except“-Klauseln genannt werden;
- Zeilen 108–130: veranschaulichen die Verwendung der Klausel finally;
- Zeilen 132–139: Diese Zeilen zeigen, dass die Klausel [except] nicht obligatorisch ist;
Die Bildschirmausgaben lauten wie folgt:
5.3. Skript [exceptions_03]
Dieses neue Skript veranschaulicht die Weiterleitung von Ausnahmen in der Kette der aufrufenden Funktionen:
# eine benutzerdefinierte Ausnahme
class MyError(BaseException):
pass
# drei Funktionen
def f1(x: int) -> int:
# Ausnahmen werden nicht behandelt – sie werden automatisch weitergeleitet
return f2(x)
def f2(y: int) -> int:
# Ausnahmen werden nicht behandelt – sie werden automatisch weitergeleitet
return f3(y)
def f3(z: int) -> int:
if (z % 2) == 0:
# Wenn z gerade ist, wird eine Ausnahme ausgelöst
raise MyError("exception dans f3")
else:
return 2 * z
# ---------- main
# Die Ausnahmen werden die Kette der aufgerufenen Methoden hinauf weitergeleitet
# bis eine Methode sie abfängt. Hier ist das „main“
try:
print(f1(4))
except MyError as erreur:
print(f"type : {type(erreur)}, arguments : {erreur.args}")
# Drei weitere Funktionen, die die weitergeleiteten Ausnahmen ergänzen
def f4(x: int) -> int:
try:
return f5(x)
except MyError as erreur:
# Die Ausnahme wird ergänzt und anschließend weitergeleitet
raise MyError("exception dans f4", erreur)
def f5(y: int) -> int:
try:
return f6(y)
except MyError as erreur:
# Die Ausnahme wird erweitert und anschließend erneut ausgelöst
raise MyError("exception dans f5", erreur)
def f6(z: int) -> int:
if (z % 2) == 0:
# Es wird eine Ausnahme ausgelöst, wenn z gerade ist
raise MyError("exception dans f6")
else:
return 2 * z
# ---------- main
try:
print(f4(4))
except MyError as erreur:
# Anzeige der Ausnahme
print(f"type : {type(erreur)}, arguments : {erreur.args}")
# Man kann den Ausnahmestapel zurückverfolgen
err = erreur
# Die Fehlermeldung wird angezeigt
print(err.args[0])
# Ist eine Ausnahme gekapselt?
while len(err.args) == 2 and isinstance(err.args[1], BaseException):
# Änderung der Ausnahme
err = err.args[1]
# Das erste Argument ist die Fehlermeldung
print(err.args[0])
Anmerkungen:
- Zeilen 25–32: Beim Aufruf von main --> f1 --> f2 --> f3 (Zeile 30) wird die von f3 ausgelöste Ausnahme MyError bis zu main weitergeleitet. Sie wird dann durch die Klausel except in Zeile 31 verarbeitet;
- Zeilen 61–75: Beim Aufruf von main --> f4 --> f5 --> f6 (Zeile 62) wird die von f6 ausgelöste Ausnahme MyError bis zu main weitergeleitet. Sie wird dann von der Klausel except in Zeile 63 verarbeitet. Diesmal ist die Ausnahme MyError, die bei der Rückverfolgung der Kette der aufrufenden Funktionen nach oben gelangt, selbst in eine andere Ausnahme gekapselt;
- Zeilen 66–75: zeigen, wie der Ausnahmestapel abgearbeitet wird;
- Zeile 71: Die Funktion [isinstance(instance, Classe)] gibt „True“ zurück, wenn das Objekt [instance] vom Typ [Classe] oder davon abgeleitet ist. Hier haben wir die oberste Ausnahme „[BaseException]“ verwendet, wodurch sichergestellt ist, dass alle Ausnahmen erfasst werden;
Die Bildschirmausgaben lauten wie folgt: