Fehler und Ausnahmen¶
Ausnahmebehandlung¶

Python unterscheidet unter anderem Syntaxfehler und Ausnahmen (Exceptions), die während der Ausführung auftreten. Eine Ausnahme ist zunächst ein Signal dafür, dass der normale Programmfluss nicht fortgesetzt werden kann. Viele Ausnahmen repräsentieren Fehlerzustände – beispielsweise ungültige Eingaben oder fehlende Dateien –, einige dienen aber auch speziellen Steuerungszwecken.
Mit try und except kann ein Programm erwartbare Ausnahmesituationen gezielt behandeln. Dabei sollte man möglichst spezifische Exception-Klassen abfangen. Ein nacktes except: fängt auch Systemausnahmen wie KeyboardInterrupt und SystemExit; in normalem Anwendungscode ist except Exception as exc: meist die bessere allgemeine Grenze.
Eine Ausnahme, für die kein passender Handler gefunden wird, propagiert durch die Aufrufkette weiter und beendet schließlich den aktuellen Programmablauf mit einem Traceback.
n = int(input("Bitte gebe eine Zahl ein: "))
Mit Hilfe der Ausnahmebehandlung können wir robusten Code für das Lesen eines Integers von der Eingabe schreiben:
while True:
try:
n = input("Bitte gebe eine ganze Zahl ein: ")
n = int(n)
break
except ValueError:
print("Keine gültige Zahl, probiere es nochmals ...")
print("Großartig! Das war eine korrekte ganze Zahl")
Es handelt sich um eine Schleife, die nur dann abbricht, wenn eine gültige Ganzzahl angegeben wurde. Die while-Schleife wird eingegeben. Der Code innerhalb der try-Klausel wird Anweisung für Anweisung ausgeführt. Tritt während der Ausführung keine Exception auf, wird die Ausführung bis zur break-Anweisung geführt und die while-Schleife verlassen. Tritt eine Exception auf, z. B. beim Casting von n, wird der Rest des try-Blocks übersprungen und die except-Klausel ausgeführt. Der ausgelöste Fehler, in unserem Fall ein ValueError, muss mit einem der Namen hinter except übereinstimmen. In unserem Beispiel nur einer, nämlich "ValueError:". Nachdem der Text der print-Anweisung ausgegeben wurde, führt die Ausführung eine weitere Schleife aus. Sie beginnt mit einer neuen Input().
Mehrere except-Klauseln¶
Eine try-Anweisung kann mehrere Handler für unterschiedliche Exception-Typen enthalten. Höchstens ein passender Handler wird ausgeführt.
Beim Öffnen und Auswerten einer Textdatei sind beispielsweise OSError (Datei-/Betriebssystemfehler) und ValueError (ungültige Zahl) naheliegende Fälle. IOError ist in modernem Python lediglich ein Alias von OSError.
try:
with open("integers.txt", encoding="utf-8") as f:
s = f.readline()
i = int(s.strip())
except OSError as exc:
print(f"I/O-Fehler: {exc}")
except ValueError:
print("Keine gültige ganze Zahl.")
except Exception as exc:
print("Unerwarteter Fehler:", type(exc).__name__, exc)
raise
Die Variable exc ist an die konkrete Exception-Instanz gebunden. OSError besitzt bei Betriebssystemfehlern zusätzliche Attribute wie errno, strerror und gegebenenfalls filename. Häufig reicht es jedoch, die Exception direkt auszugeben.
Mehrere Exception-Typen können in einer except-Klausel als Tupel angegeben werden:
try:
with open("integers.txt", encoding="utf-8") as f:
i = int(f.readline().strip())
except (OSError, ValueError) as exc:
print("Datei konnte nicht passend verarbeitet werden:", exc)
Wir wollen nun demonstrieren, was passiert, wenn wir eine Funktion innerhalb eines Try-Blocks aufrufen und innerhalb des Funktionsaufrufs eine Exception auftritt:
def f():
x = int("four")
try:
f()
except ValueError as e:
print("Hab' den Fehler :-) ", e)
print("Weiter geht's")
Hier wird die Ausnahme nicht in f(), sondern im aufrufenden try-Block abgefangen. Im nächsten Beispiel behandelt dagegen die Funktion selbst den ValueError:
def f():
try:
x = int("four")
except ValueError as e:
print("Fehler in Funktion :-) ", e)
try:
f()
except ValueError as e:
print("got it :-) ", e)
print("Weiter geht's")
Im nächsten Schritt behandeln wir die Exception zunächst in der Funktion und lösen sie anschließend mit einem nackten raise erneut aus. Dadurch bleibt der ursprüngliche Traceback erhalten und die Ausnahme kann vom Aufrufer weiterbehandelt werden:
def f():
try:
x = int("four")
except ValueError as e:
print("Fehler in Funktion :-) ", e)
raise
try:
f()
except ValueError as e:
print("got it :-) ", e)
print("Weiter geht's")
Benutzerdefinierte Ausnahmen¶
Eigene Exception-Typen sind sinnvoll, wenn ein Anwendungsbereich einen eigenen Fehlerzustand ausdrücken soll. Benutzerdefinierte Ausnahmen sollten normalerweise von Exception erben, nicht direkt von BaseException. Ein sprechender Name mit dem Suffix Error erleichtert die Verwendung:
class UngueltigesAlterError(Exception):
"""Wird ausgelöst, wenn eine Altersangabe außerhalb des erlaubten Bereichs liegt."""
def pruefe_alter(alter):
if not 0 <= alter <= 130:
raise UngueltigesAlterError(f"Unplausibles Alter: {alter}")
pruefe_alter(150)
Der neue Exception-Typ kann anschließend genauso gezielt mit except UngueltigesAlterError: behandelt werden wie eine eingebaute Ausnahme.
try:
pruefe_alter(150)
except UngueltigesAlterError as exc:
print(exc)
Aufräum-Aktionen mit finally¶
Eine finally-Klausel wird beim Verlassen des zugehörigen try-Blocks ausgeführt – unabhängig davon, ob der Block normal endet, eine Ausnahme behandelt wird oder eine Ausnahme weiterpropagiert. finally behandelt eine Ausnahme jedoch nicht automatisch; ohne passenden except-Handler wird sie nach dem finally-Block weitergereicht.
Für Ressourcen wie Dateien ist häufig ein Context Manager (with) klarer als manuelles Aufräumen in finally.
try:
x = float(input("Deine Zahl: "))
inverse = 1.0 / x
finally:
print("""Vielleicht ist eine Ausnahme
aufgetreten oder auch nicht.""")
print("Die inverse Zahl: ", inverse)
Kombination von try, except, else und finally¶
exceptbehandelt passende Ausnahmen aus demtry-Block.elseläuft nur, wenn imtry-Block keine Ausnahme aufgetreten ist.finallyläuft beim Verlassen des Konstrukts in jedem Fall.
Das else ist besonders nützlich, um Code, der selbst neue Ausnahmen auslösen kann, nicht unnötig in den geschützten try-Block aufzunehmen:
try:
x = float(input("Deine Zahl: "))
inverse = 1.0 / x
except ValueError:
print("Das war keine Zahl!")
except ZeroDivisionError:
print("Durch Null kann nicht dividiert werden.")
else:
print("Die inverse Zahl:", inverse)
finally:
print("Berechnung beendet.")
else-Klausel¶
Ein else-Block steht nach allen except-Klauseln und wird ausgeführt, wenn der try-Block ohne Ausnahme beendet wurde. Bei Dateioperationen lässt sich damit beispielsweise das Öffnen von der anschließenden Verarbeitung trennen:
file_name = "trullala.txt"
try:
fh = open(file_name, encoding="utf-8")
except OSError as exc:
print("Datei lässt sich nicht öffnen:", exc)
else:
with fh:
text = fh.readlines()
print(f"{len(text)} Zeile(n) gelesen")
Noch kompakter ist es häufig, den Context Manager direkt innerhalb von try zu verwenden, wenn die gesamte Dateioperation als eine Einheit behandelt werden soll:
file_name = "fibonacci.py"
try:
with open(file_name, encoding="utf-8") as fh:
text = fh.readlines()
except OSError as exc:
print("Datei lässt sich nicht öffnen:", exc)
else:
print(f"{len(text)} Zeile(n) gelesen")
Die assert-Anweisung¶
assert ist für interne Annahmen und Debugging-Invarianten gedacht. Formal entspricht
assert bedingung, nachricht
bei aktivem Debug-Modus ungefähr:
if not bedingung:
raise AssertionError(nachricht)
Assertions dürfen nicht für die Validierung externer Eingaben, Berechtigungsprüfungen oder andere zwingend notwendige Laufzeitkontrollen verwendet werden. Python kann Assertions bei optimierter Ausführung (python -O) vollständig entfernen.
x = 5
y = 3
assert x < y, "x has to be smaller than y"
assert ist also sinnvoll, um Annahmen des Programmierers über den internen Zustand zu dokumentieren und während Entwicklung und Tests zu prüfen. Erwartbare Benutzer- oder Dateifehler behandelt man dagegen mit normaler Validierung und geeigneten Exceptions.
