Python Exception Chaining (Implicit und Explicit)
In Python können beim Auftreten von Ausnahmen (Exceptions) Fehler an anderer Stelle im Code ausgelöst werden, während die ursprüngliche Ausnahme noch erhalten bleibt. Dies ist besonders nützlich, wenn du eine Ausnahme abfangen möchtest, aber eine neue, spezifischere Ausnahme auslösen musst.
Bernd Fischer
18. Okt 2024 · 3 Minuten Lesezeit
Exception Chaining
Exception Chaining in Python ist ein nützliches Werkzeug, um Fehler in einer klaren und nachvollziehbaren Weise zu verketten.
Die Wahl zwischen implizitem und explizitem Chaining hängt davon ab, ob du die ursprüngliche Ausnahme explizit behandeln möchtest oder nicht. In jedem Fall bietet Python mit Exception Chaining eine Möglichkeit, Fehlersituationen transparent zu machen und deren Ursache einfach nachzuvollziehen.
Implizites Exception Chaining
Das implizite Exception Chaining tritt auf, wenn innerhalb eines except-Blocks eine neue Ausnahme ausgelöst wird, ohne explizit die ursprüngliche Ausnahme zu referenzieren. Python verknüpft die ursprüngliche Ausnahme automatisch mit der neuen Ausnahme über das Attribut __context__.
Beispiel 1: Implizites Chaining
try:
1 / 0 # Löst ZeroDivisionError aus
except ZeroDivisionError:
raise ValueError("Ein Fehler trat auf.") # Löst ValueError aus
In diesem Beispiel tritt zuerst eine ZeroDivisionError-Ausnahme auf. Innerhalb des except-Blocks wird jedoch eine neue Ausnahme, ein ValueError, ausgelöst. Python verknüpft den ursprünglichen Fehler mit dem neuen, und die ursprüngliche Ausnahme ist unter dem Attribut __context__ verfügbar.
Wenn du das Programm ausführst, wird der Fehlerverlauf angezeigt, einschließlich des ursprünglichen Fehlers:
Traceback (most recent call last):
File "example.py", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "example.py", line 4, in <module>
raise ValueError("Ein Fehler trat auf.")
ValueError: Ein Fehler trat auf.
Beispiel 2: Implizites Chaining mit mehreren Ausnahmen
try:
numbers = [1, 2, 3]
print(numbers[5]) # Löst IndexError aus
except IndexError:
raise KeyError("Falscher Schlüssel.")
Auch hier tritt zuerst ein IndexError auf, weil auf ein nicht existierendes Element der Liste zugegriffen wird. Danach wird ein KeyError ausgelöst, der mit dem ursprünglichen Fehler verkettet wird:
Traceback (most recent call last):
File "example.py", line 3, in <module>
print(numbers[5])
IndexError: list index out of range
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "example.py", line 5, in <module>
raise KeyError("Falscher Schlüssel.")
KeyError: 'Falscher Schlüssel.'
Explizites Exception Chaining
Beim expliziten Exception Chaining kannst du die ursprüngliche Ausnahme explizit referenzieren, wenn du eine neue Ausnahme auslöst. Das geschieht mit dem Schlüsselwort from. Hierdurch wird die neue Ausnahme mit der alten verknüpft, und du kannst explizit festlegen, welche Ausnahme der Verursacher war.
Beispiel 1: Explizites Exception Chaining
try:
open("nicht_existierende_datei.txt") # Löst FileNotFoundError aus
except FileNotFoundError as e:
raise RuntimeError("Fehler beim Öffnen der Datei.") from e
In diesem Beispiel tritt ein FileNotFoundError auf, weil die Datei nicht existiert. Wir fangen diese Ausnahme ab und lösen explizit eine neue RuntimeError-Ausnahme aus, die mit der ursprünglichen Ausnahme FileNotFoundError verknüpft ist:
Traceback (most recent call last):
File "example.py", line 2, in <module>
open("nicht_existierende_datei.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'nicht_existierende_datei.txt'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "example.py", line 4, in <module>
raise RuntimeError("Fehler beim Öffnen der Datei.") from e
RuntimeError: Fehler beim Öffnen der Datei.
Hier wird explizit der FileNotFoundError als Ursache für den RuntimeError angegeben.
Beispiel 2: Exception Chaining mit mehreren Ebenen
Manchmal kann eine Ausnahme mehrfach verkettet werden, z. B. wenn mehrere Ausnahmen in verschiedenen Ebenen des Programms auftreten. Das folgende Beispiel zeigt, wie man eine Kette von Ausnahmen erstellt:
try:
raise ValueError("Ursprünglicher Fehler.")
except ValueError as e:
try:
raise TypeError("Ein weiterer Fehler.") from e
except TypeError as te:
raise RuntimeError("Endgültiger Fehler.") from te
Hier tritt eine Kette von Ausnahmen auf: Zuerst ValueError, dann TypeError, und schließlich RuntimeError, alle miteinander verkettet.
Traceback (most recent call last):
File "example.py", line 4, in <module>
raise ValueError("Ursprünglicher Fehler.")
ValueError: Ursprünglicher Fehler.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "example.py", line 7, in <module>
raise TypeError("Ein weiterer Fehler.") from e
TypeError: Ein weiterer Fehler.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "example.py", line 9, in <module>
raise RuntimeError("Endgültiger Fehler.") from te
RuntimeError: Endgültiger Fehler.
context und cause
In Python gibt es zwei Attribute, die sich mit Exception Chaining beschäftigen: __context__ und __cause__. Beide spielen eine Rolle beim Verketten von Ausnahmen, aber sie werden in unterschiedlichen Situationen verwendet.
Hier sind die Unterschiede:
__context__:
- Wird gesetzt, wenn eine Ausnahme innerhalb eines
except-Blocks auftritt (implizites Exception Chaining). - Python füllt
__context__automatisch aus, wenn du eine neue Ausnahme auslöst, während du eine andere Ausnahme abfängst. __context__zeigt auf die ursprüngliche Ausnahme, die nicht explizit angegeben wurde.
__cause__:
- Wird verwendet, wenn explizites Exception Chaining mit
raise ... from ...auftritt. - Wenn du
raise NewException from OldExceptionschreibst, wird die alte Ausnahme (OldException) in das Attribut__cause__der neuen Ausnahme (NewException) gespeichert. __cause__hebt die Verkettung explizit hervor und gibt dir mehr Kontrolle über die Ausnahmeursache.
Beispiel für __context__
Hier wird gezeigt, wie __context__ in einem Fall von implizitem Exception Chaining verwendet wird:
try:
try:
1 / 0 # Löst ZeroDivisionError aus
except ZeroDivisionError:
raise ValueError("Fehler in der Berechnung") # Neue Ausnahme wird ausgelöst
except ValueError as e:
print("ValueError aufgetreten:", e)
if e.__context__:
print("Ursprüngliche Ausnahme (__context__):", e.__context__)
Erklärung:
ZeroDivisionErrorwird im innerentry-Block ausgelöst.- Im
except-Block wird eine neue AusnahmeValueErrorausgelöst. - Python verknüpft die neue Ausnahme implizit mit der ursprünglichen Ausnahme und speichert den
ZeroDivisionErrorin__context__.
Ausgabe:
ValueError aufgetreten: Fehler in der Berechnung
Ursprüngliche Ausnahme (__context__): division by zero
Beispiel für __cause__
Im folgenden Beispiel verwenden wir explizites Exception Chaining mit raise ... from ...:
try:
try:
open("nicht_existierende_datei.txt") # Löst FileNotFoundError aus
except FileNotFoundError as e:
raise RuntimeError("Fehler beim Öffnen der Datei") from e
except RuntimeError as e:
print("RuntimeError aufgetreten:", e)
if e.__cause__:
print("Ursprüngliche Ausnahme (__cause__):", e.__cause__)
Erklärung:
FileNotFoundErrorwird ausgelöst, da die Datei nicht existiert.- Eine neue Ausnahme
RuntimeErrorwird explizit mitfromverknüpft. - Die ursprüngliche Ausnahme
FileNotFoundErrorwird in__cause__gespeichert.
Ausgabe:
RuntimeError aufgetreten: Fehler beim Öffnen der Datei
Ursprüngliche Ausnahme (__cause__): [Errno 2] No such file or directory: 'nicht_existierende_datei.txt'