Häufige Fallstricke vermeiden: Richtige Exception-Behandlung in Python
Ich sehe viele Leute, die Exceptions falsch behandeln. Vielleicht trifft das auch auf Sie zu. Kommt Ihnen folgende Situation bekannt vor?
Sie schreiben Code, wissen aber, dass die verwendete Bibliothek eine Exception werfen könnte. Sie erinnern sich nicht genau, welche. Jetzt ist es verlockend, sogenannte Catch-All-Blöcke zu nutzen und mit dem Spaß weiterzumachen.
Die schlechteste Art
Das Schlimmste: Ein try-except-Block, der alles fängt. Mit Catch-All meine ich so etwas:
try:
...
except:
pass
Catch-All-Blöcke wie diese sind schlecht, weil:
- Sie keine Ahnung haben, welche anderen Exceptions geworfen werden könnten (dazu später mehr).
- Die Exception wird durch stillschweigendes
passversteckt, statt den Fehler zu loggen.
Außerdem fängt ein leeres except alles ab, inklusive KeyboardInterrupt (Strg+C), SystemExit und sogar NameError! Das bedeutet, folgender Code kann nicht sauber gestoppt werden:
from time import sleep
while True:
try:
print("Versuch, mich zu stoppen")
sleep(1)
except:
print("Hör.. auf.. mich jetzt!")
Probieren Sie es aus. Sie müssen das Terminalfenster schließen oder den Python-Prozess killen, um das Programm zu stoppen.
Eine etwas bessere Art, alle Exceptions zu fangen
Im Gegensatz dazu: Bei except Exception können Sie den laufenden Prozess zwar immer noch nicht sauber stoppen, aber zumindest SystemExit, KeyboardInterrupt und ähnliche Exceptions werden nicht gefangen:
from time import sleep
while True:
try:
print("Versuch, mich zu stoppen")
sleep(1)
except Exception:
print("Ok, ich höre auf!")
Warum fängt except Exception nicht SystemExit, KeyboardInterrupt etc.?
Alle Exceptions erben von der Klasse BaseException. Laut offizieller Dokumentation: „In einem try-Statement mit einer except-Klausel, die eine bestimmte Klasse nennt, behandelt diese Klausel auch alle von dieser Klasse abgeleiteten Exception-Klassen.“ Ein leeres except ist äquivalent zu except BaseException, fängt also alle möglichen Exceptions.
Im Gegensatz dazu ist die Klasse Exception definiert als: „Alle eingebauten, nicht-systembeendenden Exceptions leiten sich von dieser Klasse ab. Alle benutzerdefinierten Exceptions sollten ebenfalls davon abgeleitet sein.“
Es wird noch schlimmer
Im folgenden Beispiel nutzen wir die os-Bibliothek, um das aktuelle Arbeitsverzeichnis zu erhalten. Doch meine dicken Finger machten einen Tippfehler:
import os
try:
working_dir = os.getcdw()
print(working_dir)
except:
print('Fehler')
Da os.getcdw keine Funktion im os-Modul ist, wird ein NameError geworfen. Statt zu scheitern, fängt die except-Klausel den Fehler ab, gibt „Fehler“ aus und das Programm läuft trotz offensichtlichem Tippfehler weiter. Leider löst auch except Exception das nicht!
Unser kleiner Trick aus Schritt 1 ist also nicht die Lösung für alle Probleme. Was sollten wir also tun?
Fangen, was man handhaben kann
Ein oft gehörter Satz über Exceptions: Fang nur, was du handhaben kannst. Viele Entwickler sind versucht, Exceptions direkt zu behandeln, aber oft ist es besser, die Exception an einen Programmteil weiterzureichen, der sie tatsächlich handhaben kann.
Beispiel: Der Teil eines Texteditors, der Datei öffnet und lädt, nennen wir OpenFile-Klasse. Wenn der User eine nicht existierende Datei öffnen will, können Sie den Fehler direkt behandeln oder weiterreichen.
Hier ist es besser, die Exception an den Aufrufer weiterzureichen, weil OpenFile nicht weiß, wie schwer dieser Fehler für den Aufrufer ist. Der Aufrufer kann die Situation auf verschiedene Weisen handhaben:
- Eine neue Datei mit diesem Namen erstellen und weitermachen
- Vielleicht braucht der Aufrufer die Datei, dann kann er einen Fehlerdialog anzeigen, dass die Datei nicht existiert.
Auf jeden Fall: Es liegt nicht an der OpenFile-Klasse zu entscheiden, was bei FileNotFoundError passiert.
Soll eine Exception immer weitergereicht werden? Nein. Eine mögliche Exception, die in der FileOpen-Klasse behandelt werden kann, ist TimeoutError. Man möchte vielleicht ein paar Mal wiederholen, ohne den Aufrufer mit dem Fehler zu belästen. Das ist eine Exception, die OpenFile handhaben kann, also OK, sie zu fangen und zu wiederholen.
Fazit
Sie sollten unter keinen Umständen mehr Exceptions fangen, als Sie handhaben können. Blanket-except-Blöcke sind ein Rezept für Bugs und unvorhersehbaren Code. Anders gesagt: Fangen Sie nur, was Sie handhaben können.
Wenn Sie Code mit dem Mantra „Fang, was du handhaben kannst“ schreiben, brechen Catch-All-Blöcke alle Regeln. Also bitte: Hören Sie auf damit. Als Übung könnten Sie bestehenden Code revisiten und prüfen, ob er mit diesem Wissen verbessert werden kann!
Python
- Python-Operatoren meistern: Umfassender Leitfaden
- Python-Fehler & eingebaute Exceptions meistern
- Python benutzerdefinierte Exceptions: Ein praktischer Leitfaden
- Aktuelles Datum und Uhrzeit in Python abrufen: Praktischer Leitfaden
- Multi-Exception Handling in Java meistern
- CMMS-Training meistern: Teams engagieren & Retention boostern
- Python print()-Funktion: Praktischer Leitfaden mit Beispielen
- Python Dictionaries: Hinzufügen, Aktualisieren und Verwalten von Key/Value-Paaren
- Python Print ohne Newline: Den end-Parameter & andere Techniken meistern
- Durchschnitt berechnen in Python: Praktischer Leitfaden