Grundlagen

Exceptions in Python: try, except, else, finally, raise und eigene Ausnahmen

Aktualisiert: · Skillaria.Top

Eine Ausnahme (Exception) in Python ist ein Objekt, das einen Fehler meldet und den normalen Ablauf unterbricht, bis ein try/except-Block sie abfängt; else läuft nur, wenn kein Fehler auftrat, finally immer. Eine Ausnahme lösen Sie selbst mit raise aus, etwa raise ValueError("Betrag negativ"), und eine eigene Ausnahme erstellen Sie als Klasse, die von Exception erbt.

Eine Ausnahme (Exception) in Python ist ein Objekt, das einen Fehler meldet und den normalen Ablauf des Programms unterbricht. Wird sie nicht abgefangen, gibt Python einen Traceback aus und beendet das Programm. Abgefangen werden Ausnahmen mit dem Konstrukt try/except, selbst ausgelöst werden sie mit raise. Wir sehen uns an, wie das funktioniert, welche Fehler am häufigsten auftreten und wie man die Fehlerbehandlung nicht in das Verstecken von Fehlern verwandelt.

Was eine Ausnahme in Python ist

Wenn eine Operation nicht ausgeführt werden kann, etwa bei einer Division durch null, beim Zugriff auf einen nicht vorhandenen Schlüssel oder bei der Umwandlung eines Wortes in eine Zahl, erzeugt Python ein Ausnahmeobjekt und „wirft“ es. Die aktuelle Funktion bricht ab, die Ausnahme wandert durch die Aufrufkette nach oben, bis sie auf einen passenden Handler trifft. Findet sich keiner, stürzt das Programm ab.

>>> 10 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Übrigens: Auf die häufige Frage, welche Ausnahme bei einer Division durch null in Python auftritt, lautet die Antwort ZeroDivisionError. Das gilt für /, // und %.

Wie man einen Traceback liest

Der Traceback ist ein Bericht darüber, wie das Programm bis zum Fehler gelangt ist. Man liest ihn am besten von unten nach oben. Die letzte Zeile ist das Wichtigste: der Typ der Ausnahme und die Meldung. Darüber steht die Codezeile, in der es passiert ist, und darüber, wer sie aufgerufen hat, bis zum Einstiegspunkt.

Traceback (most recent call last):
  File "tracker.py", line 9, in <module>
    total = parse_amount(row)
  File "tracker.py", line 4, in parse_amount
    return float(raw.strip())
           ~~~~~^^^^^^^^^^^^^
ValueError: could not convert string to float: 'hundert'

Seit Python 3.11 werden unter der Codezeile Markierungen wie ~~~^^^ gezeigt (PEP 657). Sie zeigen genau an, welcher Ausdruck in der Zeile fehlgeschlagen ist. Das hilft besonders, wenn in einer Zeile mehrere Aufrufe hintereinander stehen.

Hinweise „Did you mean“

Seit Python 3.10 schlägt die Fehlermeldung bei einem Tippfehler im Namen eine ähnliche Schreibweise vor. In Python 3.15 sind diese Hinweise noch klüger: Der Interpreter kennt Gewohnheiten aus anderen Sprachen. Der Aufruf list.push führt zum Vorschlag append, "Kaffee".toUpperCase() zu upper. Der Hinweis berücksichtigt auch verschachtelte Felder: Liegt ein Dictionary im Attribut totals eines Objekts und Sie rufen items() direkt am Objekt auf, schlägt der Fehler .totals.items vor. Ähnliche Namen schlägt jetzt auch delattr() vor.

Typen von Ausnahmen in Python

Es gibt Dutzende eingebaute Ausnahmen, am Anfang begegnen Ihnen aber meist diese:

AusnahmeWann sie auftrittBeispiel
NameErrorName nicht definiert, meist ein Tippfehlerprint(summe)
TypeErrorOperation passt nicht zu diesem Typ"5" + 3
ValueErrorTyp stimmt, der Wert nichtint("hundert")
KeyErrorSchlüssel fehlt im Dictionaryd["missing"]
IndexErrorIndex außerhalb der Liste[1, 2][5]
AttributeErrorObjekt hat keine solche Methode oder kein solches Feld[].push(1)
ZeroDivisionErrorDivision durch null1 / 0
FileNotFoundErrorDatei nicht gefundenopen("nope.txt")

Eine Sonderstellung hat SyntaxError (und sein Spezialfall IndentationError). Er tritt schon beim Einlesen der Datei auf, vor dem Start des Codes, deshalb lässt er sich in derselben Datei nicht mit try abfangen.

Die Hierarchie der Ausnahmen

Ausnahmen sind Klassen und bilden eine Hierarchie. Ganz oben steht BaseException. Davon erben Exception (fast alle gewöhnlichen Fehler) und einige Systemausnahmen: KeyboardInterrupt, SystemExit, GeneratorExit.

BaseException
 ├── KeyboardInterrupt
 ├── SystemExit
 └── Exception
      ├── ArithmeticError
      │    └── ZeroDivisionError
      ├── LookupError
      │    ├── KeyError
      │    └── IndexError
      ├── OSError
      │    └── FileNotFoundError
      ├── TypeError
      └── ValueError

Die Hierarchie ist beim Abfangen wichtig: except LookupError fängt sowohl KeyError als auch IndexError. KeyboardInterrupt ist kein Fehler im Code. Damit meldet Python, dass der Benutzer Strg+C gedrückt hat. Die Ausnahme erbt bewusst nicht von Exception, damit ein except Exception das Abbrechen des Programms nicht verhindert.

Fehlerbehandlung: try/except/else/finally

Code, der fehlschlagen kann, kommt in den Block try. Im Block except geben Sie den Fehlertyp an und legen fest, was passieren soll. Genau dieser except-Block dient in Python der Behandlung von Ausnahmen.

def parse_amount(raw):
    try:
        value = float(raw)
    except ValueError as e:
        print(f"Zeile wird übersprungen: {e}")
        return None
    else:
        return value          # wird ausgeführt, wenn kein Fehler auftrat
    finally:
        print("Analyse abgeschlossen")   # wird immer ausgeführt

print(parse_amount("250"))

Die Ausgabe lautet hier: zuerst „Analyse abgeschlossen“, dann 250.0. Ein return im Block else merkt sich den Wert, danach führt Python auf jeden Fall finally aus und gibt erst dann das Ergebnis zurück. Deshalb ist finally verlässlich für Aufräumarbeiten, etwa zum Schließen einer Datei oder zum Freigeben einer Verbindung.

Schreiben Sie in finally ein eigenes return, überschreibt es das vorherige und kann die Ausnahme sogar verschlucken. Seit Python 3.14 gibt es für solchen Code eine SyntaxWarning (PEP 765). Schreiben Sie also kein return in finally.

Mehrere Ausnahmen gleichzeitig

Sie können mehrere except-Blöcke für verschiedene Typen schreiben oder die Typen in einem Tupel aufzählen:

try:
    amount = int(data["amount"])
except KeyError:
    amount = 0
except (ValueError, TypeError) as e:
    print("Fehlerhafter Betrag:", e)
    amount = 0

Die Blöcke werden von oben nach unten geprüft, der erste passende wird ausgeführt. Deshalb stehen spezifischere Typen vor allgemeinen. Seit Python 3.14 (PEP 758) dürfen mehrere Typen auch ohne Klammern stehen, also except ValueError, TypeError:. Mit as bleiben die Klammern aber nötig.

Wie man den Text einer Ausnahme ausgibt und ihren Typ ermittelt

Das Ausnahmeobjekt erhalten Sie über as e. str(e) liefert die Meldung, type(e).__name__ den Namen des Typs:

try:
    int("hundert")
except ValueError as e:
    print(type(e).__name__)   # ValueError
    print(e)                  # invalid literal for int() with base 10: 'hundert'

Das Modul traceback: vollständiger Fehlertext

Manchmal muss man eine Ausnahme abfangen, den vollständigen Traceback aber trotzdem erhalten, zum Beispiel für ein Protokoll oder einen Bericht. Dafür gibt es das Modul traceback:

import traceback

try:
    1 / 0
except ZeroDivisionError:
    traceback.print_exc()             # gibt den Traceback auf stderr aus
    text = traceback.format_exc()     # derselbe Text als String

Wenn Sie über das Modul logging protokollieren, rufen Sie innerhalb von except besser logging.exception("Berechnung fehlgeschlagen") auf. Der Traceback wird dann automatisch an die Meldung angehängt.

Python raise: eine Ausnahme selbst auslösen

Die Anweisung raise wirft eine Ausnahme von Hand. So teilt eine Funktion mit, dass sie mit diesen Daten nicht arbeiten kann:

def add_expense(amount):
    if amount < 0:
        raise ValueError(f"Der Betrag darf nicht negativ sein: {amount}")
    ...

add_expense(-5)
# ValueError: Der Betrag darf nicht negativ sein: -5

Python raise ist eine Anweisung und keine Funktion, deshalb stehen hinter dem Wort raise keine Klammern als Aufruf. Die Argumente in der Klammer gehen an den Konstruktor der Ausnahme. Wählen Sie den Typ nach seiner Bedeutung: falscher Wert, ValueError; falscher Typ, TypeError.

raise ohne Argumente und raise from

Ein nacktes raise innerhalb von except gibt die aktuelle Ausnahme unverändert weiter. Das braucht man, wenn man etwas protokollieren, die Behandlung aber dem aufrufenden Code überlassen will.

Wenn Sie eine Ausnahme durch eine andere ersetzen, verwenden Sie raise ... from e. Die ursprüngliche Ausnahme bleibt dann in der Kette erhalten, und der Traceback zeigt beide:

def load_config(path):
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError as e:
        raise RuntimeError(f"Keine Konfiguration {path}") from e

add_note: den Kontext zur Fehlermeldung ergänzen

Seit Python 3.11 (PEP 678) können Sie einer Ausnahme mit der Methode add_note() eine Notiz hinzufügen und sie dann weiterwerfen. Der Typ des Fehlers bleibt gleich, die Notiz wird unter der Meldung ausgegeben:

def parse(raw, line_no):
    try:
        return float(raw)
    except ValueError as e:
        e.add_note(f"Datei expenses.csv, Zeile {line_no}")
        raise

parse("hundert", 17)
# ValueError: could not convert string to float: 'hundert'
# Datei expenses.csv, Zeile 17

Eine eigene Ausnahme in Python erstellen

Eigene Ausnahmen braucht man für Fehler des Fachgebiets: Budget überschritten, Bestellung bereits bezahlt, Benutzer gesperrt. Dafür genügt eine Klasse, die von Exception erbt:

class BudgetExceeded(Exception):
    def __init__(self, category, limit, spent):
        super().__init__(f"{category}: Limit {limit}, ausgegeben {spent}")
        self.category = category
        self.limit = limit
        self.spent = spent

def check_budget(category, limit, spent):
    if spent > limit:
        raise BudgetExceeded(category, limit, spent)

try:
    check_budget("Essen", 10000, 12500)
except BudgetExceeded as e:
    print("Warnung:", e)   # Warnung: Essen: Limit 10000, ausgegeben 12500

Jetzt fängt der aufrufende Code genau die Überschreitung des Budgets ab und verwechselt sie nicht mit Tippfehlern. In einem großen Projekt lohnt sich eine gemeinsame Basisklasse (class TrackerError(Exception)), von der alle eigenen Ausnahmen erben. Dann lassen sie sich mit einem einzigen except TrackerError abfangen. Erben Sie von Exception und nicht von BaseException.

Ausnahmegruppen und except*

Manchmal treten gleichzeitig mehrere Fehler auf, etwa bei der Prüfung einer ganzen Datei oder bei parallelen Aufgaben mit asyncio.TaskGroup. Seit Python 3.11 lassen sie sich in einer ExceptionGroup sammeln und mit except* nach Typ behandeln:

def check_all(rows):
    errors = []
    for r in rows:
        try:
            float(r)
        except ValueError as e:
            errors.append(e)
    if errors:
        raise ExceptionGroup("Fehler in der Datei", errors)

try:
    check_all(["1", "a", "b"])
except* ValueError as eg:
    print(f"Fehlerhafte Zeilen: {len(eg.exceptions)}")   # Fehlerhafte Zeilen: 2

Python try except für alle Fehler: warum das schlecht ist

Die schädlichste Gewohnheit ist, alles abzufangen: except: pass. Das Programm „stürzt nicht mehr ab“, meldet Probleme aber auch nicht mehr. Ein nacktes except fängt sogar KeyboardInterrupt und SystemExit, sodass sich das Programm nicht mehr mit Strg+C beenden lässt. except Exception: pass ist etwas besser, verschluckt aber trotzdem stillschweigend Tippfehler in Namen und Logikfehler.

So behandelt man Ausnahmen in Python richtig:

  • Fangen Sie nur den Typ ab, mit dem Sie umgehen können: except ValueError statt allem.
  • Halten Sie den Block try kurz, nur die Zeilen, die genau so fehlschlagen können.
  • Wenn Sie breit abfangen (except Exception), dann auf oberster Ebene des Programms und mit Protokollierung des Tracebacks.
  • Verwenden Sie Ausnahmen nicht für gewöhnliche Verzweigungen, wo eine Prüfung genügt: In einfachen Fällen ist dict.get() besser als try/except KeyError.
  • Bei Ressourcen nehmen Sie with. Es schließt die Datei auch dann, wenn ein Fehler auftritt.

Fazit

Ausnahmen in Python sind ein normaler Mechanismus, um Fehler zu melden, und kein Grund, sie zu verstecken. Lesen Sie den Traceback von unten nach oben, fangen Sie bestimmte Typen in try/except ab, verwenden Sie else für den Code „wenn alles geklappt hat“ und finally für Aufräumarbeiten. Mit Python raise lösen Sie selbst Fehler aus, raise from und add_note bewahren den Kontext, und eigene Ausnahmen erben von Exception. Im Python-Kurs wird das Thema praktisch behandelt: Der Ausgabentracker stürzt nicht mehr wegen einer fehlerhaften CSV-Zeile ab und meldet eine Budgetüberschreitung über eine eigene Ausnahme.

Häufige Fragen

Worin unterscheidet sich ein Fehler von einer Ausnahme in Python?

Im Alltag sind beide Wörter Synonyme. Technisch wird ein SyntaxError schon beim Einlesen des Codes erkannt, die übrigen Fehler treten zur Laufzeit als Ausnahmeobjekte auf, die man abfangen kann.

Kann die Ausführung nach einer Ausnahme an derselben Stelle weitergehen?

Nein. Nach dem Abfangen läuft das Programm hinter dem Block try/except weiter, nicht an der Stelle des Fehlers. Wenn Sie jede Zeile einer Datei behandeln wollen, setzen Sie das try in die Schleife.

Was passiert, wenn eine Ausnahme innerhalb von except auftritt?

Sie wird weitergereicht, und die ursprüngliche Ausnahme bleibt im Attribut __context__ erhalten. Im Traceback erscheint dann der Satz „During handling of the above exception, another exception occurred“.

Wie prüfe ich in Tests, dass eine Funktion eine Ausnahme auslöst?

In pytest nutzt man with pytest.raises(ValueError):, in unittest self.assertRaises(ValueError). Der Test besteht nur, wenn im Block eine Ausnahme des angegebenen Typs auftritt.

Was ist assert und worin unterscheidet er sich von raise?

assert bedingung, "Meldung" wirft einen AssertionError, wenn die Bedingung falsch ist. Das ist ein Werkzeug für Prüfungen beim Entwickeln: Mit dem Schalter -O werden asserts abgeschaltet, deshalb prüfen Sie Eingaben von Benutzern besser mit if und raise.

Das Thema im Video lernen

Dieses Thema wird im Kurs „Python 3.15: от новичка до профи“ ausführlich mit Praxis behandelt.

Zum Kurs