Exceptions (try/except/else/finally, raise, eigene Exceptions)
Track Python · PCAP-Fundament · ca. 50 Min.
Worum es geht
Fehlerbehandlung kennst du aus JS/TS mit try/catch/finally. Python hat dasselbe Grundgerüst, aber mit drei Unterschieden: Du fängst Fehler nach Typ (Klassenhierarchie), es gibt einen zusätzlichen else-Zweig, und du kannst nur Objekte von Exception-Klassen auslösen, nicht beliebige Werte. Laut Quelle ist das PCAP-Block 2: Exceptions mit 5 Fragen und 14 % (Stand der Abfrage 1. Oktober 2026, laut Glossar). Dazu ist es Alltag in jedem Backend: Retry, Validierung, eigene Fehlerklassen. Nach der Lektion kannst du den Ablauf von try/except/else/finally vorhersagen, Fehler richtig verketten und eigene Exceptions schreiben.
Ablauf: erst die Übersetzung JS/TS nach Python, dann fünf kleine Schritte mit lauffähigem Code, dann fünf Übungen.
Von JS/TS her gedacht
| Thema | JS/TS | Python |
|---|---|---|
| Fehler auslösen | throw mit beliebigem Wert (throw "x" geht) |
raise nur mit Exception-Objekt oder -Klasse |
| Fangen | ein catch (e) für alles, Typ per instanceof prüfen |
mehrere except Typ, der erste passende gewinnt |
| Kein Fehler | kein eigener Zweig | else läuft nur, wenn try ohne Exception endete |
| Aufräumen | finally |
finally (läuft auch nach return, break) |
| Weiterreichen | throw e |
raise allein im except-Block |
| Ursache verketten | new Error("x", { cause: e }) |
raise X from Y |
| Eigene Fehlerklasse | class MyError extends Error |
class MyError(Exception) |
Dieselbe Idee in beiden Sprachen, Typ prüfen:
// TypeScript: ein catch, dann per instanceof unterscheiden
try {
JSON.parse("{kaputt");
} catch (e) {
if (e instanceof SyntaxError) console.log("kein JSON");
else throw e;
}Der else throw e aus TS entfällt in Python: Was nicht zu einem except passt, läuft von selbst weiter nach oben.
Konzept in kleinen Schritten
Schritt 1: Ablauf von try, except, else, finally
- try: Code, der einen Fehler auslösen kann.
- except: fängt passende Exceptions. Mehrere Typen mit
except (A, B):, das Objekt mitas e. Statt des Tupels darf dort auch eine Variable stehen, die ein Tupel von Klassen enthält (erlaubt = (A, B)und dannexcept erlaubt:). Das brauchst du in Übung 5. - else: läuft nur, wenn im
trykeine Exception auftrat. - finally: läuft immer, auch nach
return,breakoder einer nicht gefangenen Exception.
Die Ausgabe: für "4" kommt kein Fehler: 2.5 und immer, für "abc" kommt keine Zahl und immer, für "0" kommt durch null: division by zero und immer.
Warum else, wenn man den Code auch ans Ende von try schreiben könnte? Weil alles im try Fehler fangen darf. In else landen nur Fehler, die du nicht fangen willst.
Schritt 2: Hierarchie und Reihenfolge
Exceptions bilden eine Klassenhierarchie. Ein except mit einer Oberklasse fängt auch alle Unterklassen. Auszug aus der Quelle:
BaseException
├─ SystemExit
├─ KeyboardInterrupt
├─ GeneratorExit
└─ Exception
├─ ArithmeticError
│ ├─ ZeroDivisionError
│ └─ OverflowError
├─ LookupError
│ ├─ IndexError
│ └─ KeyError
├─ ValueError
├─ TypeError
├─ NameError ... AttributeError ... OSError ...
Der erste passende except gewinnt. Darum zuerst die spezifischen, dann die allgemeinen Typen.
except Exception fängt fast alles, aber nicht KeyboardInterrupt und SystemExit. Ein nacktes except: fängt auch diese und gilt als schlechter Stil (bare except). Ein nützliches Detail: LookupError fängt KeyError und IndexError zusammen.
Schritt 3: raise, assert, Verkettung
raise ValueError("text")löst aus.raiseallein imexcept-Block reicht dieselbe Exception weiter (nach Protokollieren zum Beispiel).raise Neu from Altverkettet: Die Ursache steht danach inNeu.__cause__.assert x > 0, "Meldung"löst einenAssertionErroraus, wenn die Bedingung falsch ist.
e.args liefert die Argumente als Tupel, str(e) den Text. Anders als throw "x" in JS geht raise "text" nicht: Python meldet einen TypeError.
Schritt 4: Eigene Exceptions
Eine eigene Exception erbt von Exception. Mit __init__ und super().__init__(msg) gibst du Zusatzdaten mit.
Ohne eigenes __init__ reicht class MyError(Exception): pass. Erbst du von einer passenden Oberklasse wie ValueError, fangen auch bestehende except ValueError-Blöcke deinen Fehler mit.
Schritt 5: finally und die Fallen
Ein return im finally überschreibt ein return im try und verschluckt sogar eine laufende Exception. Wirft ein except-Block selbst eine neue Exception, läuft finally trotzdem, bevor sie nach oben geht:
Der Name nach as gilt nur im except-Block:
Falle: die Prüfungs- und Praxisklassiker
- Falsche Reihenfolge:
except Exceptionvorexcept ZeroDivisionError, der spezifische Block wird nie erreicht. returnimfinallyüberschreibt dasreturnimtryund verschluckt Exceptions.- Bare
except:fängt auchKeyboardInterruptundSystemExit. finallyläuft immer, auch wenn derexcept-Block selbst eine neue Exception auslöst.as egilt nur im Block: danach istegelöscht (NameError).- Zu breit fangen: Ein
except ValueErrorum zu viel Code fängt auch Fehler, die du gar nicht meinst. Wenn eine eigene Exception vonValueErrorerbt, gilt das erst recht. raiseohne Typ außerhalb einesexcept-Blocks hat nichts zum Weiterreichen (RuntimeError).
Übungen
Übung 1: Ablauf vorhersagen
Der Code steht fest, du musst nur das Ergebnis vorhersagen:
def ablauf(eingabe, log):
try:
log.append("try")
zahl = int(eingabe)
wert = 10 // zahl
except ValueError:
log.append("value")
return "v"
except ZeroDivisionError:
log.append("zero")
return "z"
else:
log.append("else")
return wert
finally:
log.append("finally")
log = []
ergebnisse = [ablauf(x, log) for x in ["5", "0", "abc"]]Trage das Tupel (ergebnisse, log) ein.
Gehe die drei Eingaben einzeln durch. Welche Zeile wirft jeweils zuerst, und welche Zweige laufen danach? Überlege auch, ob finally nach einem return noch etwas schreibt.
antwort = (
[2, "z", "v"],
["try", "else", "finally", "try", "zero", "finally", "try", "value", "finally"],
)
antwortÜbung 2: Exception im except-Block (Multiple Choice)
log = []
try:
try:
1 / 0
except ZeroDivisionError:
log.append("a")
raise ValueError("neu")
finally:
log.append("b")
except ValueError:
log.append("c")Welche Reihenfolge hat log danach? Gib den Buchstaben als String zurück.
- A:
["a", "c", "b"] - B:
["b", "a", "c"] - C:
["a", "b", "c"] - D:
["c", "a", "b"]
Die neue ValueError entsteht im inneren except. Der äußere except ValueError kann sie erst fangen, wenn sie das innere try-Statement verlassen hat. Was muss das innere Statement vorher noch erledigen?
antwort = "C"
antwortÜbung 3: Eigene Exception und Verkettung
Schreibe die Exception-Klasse AlterFehler und die Funktion lies_alter(text).
AlterFehlerist eine Art vonValueError. Sie wird mitAlterFehler(wert, grund)erzeugt, speichert beide Werte als Attributewertundgrund, undstr(e)istf"{grund}: {wert!r}".lies_alterwandelttextin eine ganze Zahl um und gibt sie zurück. Leerzeichen am Rand (" 7 ") verarbeitetintselbst, du musst nichts abschneiden.- Ist der Text keine ganze Zahl, löst sie
AlterFehler(text, "keine Zahl")aus, mit dem ursprünglichen Fehler als Ursache (__cause__). - Liegt die Zahl nicht zwischen 0 und 150 (beide inklusive), löst sie
AlterFehler(text, "ausserhalb 0 bis 150")aus. - Andere Fehler (zum Beispiel
Noneals Eingabe) sollen nicht umgewandelt werden, sondern unverändert nach oben gehen.
Die letzte Zeile gibt beides als Tupel zurück.
Für __init__ hilft Schritt 4. Für die Ursache brauchst du das from aus Schritt 3. Teste im Kopf: Was passiert mit deinem eigenen Fehler, wenn er innerhalb des try-Blocks ausgelöst wird, dessen except du gerade geschrieben hast?
class AlterFehler(ValueError):
def __init__(self, wert, grund):
super().__init__(f"{grund}: {wert!r}")
self.wert = wert
self.grund = grund
def lies_alter(text):
try:
alter = int(text)
except ValueError as alt:
raise AlterFehler(text, "keine Zahl") from alt
if not 0 <= alter <= 150:
raise AlterFehler(text, "ausserhalb 0 bis 150")
return alter
(AlterFehler, lies_alter)Übung 4: Fehler finden
Diese Funktion soll den Mittelwert einer Gruppe liefern. Sie hat mehrere Fehler. Soll-Verhalten:
- Gruppe unbekannt (
KeyError): Rückgabe"unbekannt". - Gruppe leer (
ZeroDivisionError): Rückgabe0.0. - Jeder andere Fehler (zum Beispiel ein String in der Liste,
TypeError) geht nach oben. - Bei jedem Aufruf, auch bei einem Fehler, kommt der Gruppenname genau einmal in
protokoll.
Repariere die Funktion.
Spiele für jeden der vier Soll-Punkte einen Aufruf im Kopf durch und notiere, was der Code zurückgibt. Wo weicht das Ergebnis ab, und welche Zeile ist dafür verantwortlich? Es steckt mehr als ein Fehler drin.
def mittelwert(daten, gruppe, protokoll):
try:
werte = daten[gruppe]
return sum(werte) / len(werte)
except KeyError:
return "unbekannt"
except ZeroDivisionError:
return 0.0
finally:
protokoll.append(gruppe)
mittelwertÜbung 5: Wiederholen bei Fehlern (Retry)
Schreibe wiederhole(funktion, versuche, erlaubt). Sie ruft funktion() ohne Argumente auf, höchstens versuche Mal (mindestens 1).
- Klappt ein Aufruf, wird dessen Rückgabewert zurückgegeben (auch
Noneoder0). - Löst
funktioneine Exception aus, die zum Tupelerlaubtpasst (auch Unterklassen), wird erneut versucht. - Ist der letzte Versuch gescheitert, geht dieselbe Exception unverändert nach oben.
- Passt die Exception nicht zu
erlaubt, geht sie sofort nach oben, ohne weiteren Versuch.
Wo entscheidest du, ob ein Fehler “der letzte” war? Und welche Schreibweise von raise behält genau das ursprüngliche Objekt? Was ist der Unterschied zwischen except erlaubt: und except Exception:?
def wiederhole(funktion, versuche, erlaubt):
for versuch in range(1, versuche + 1):
try:
return funktion()
except erlaubt:
if versuch == versuche:
raise
wiederholeMerksatz und Prüfstein
Merksatz: Der erste passende except gewinnt (spezifisch vor allgemein), else läuft nur ohne Fehler, finally läuft immer (und ein return darin überschreibt alles), und raise allein reicht das Original weiter.
Prüfstein (offene Frage): Du schreibst try: ... except Exception: pass um einen ganzen Funktionsaufruf. Welche drei Dinge können dadurch unbemerkt schiefgehen, und wie würdest du es mit Typen, else und raise besser machen?
Quelle: quellen/python-glossar-pcap-pcpp1.md, Abschnitt “Exceptions” (Ablauf, Hierarchie, Typische Auslöser, Prüfungsfallen) und Prüfungsübersicht (Block 2: 5 Fragen, 14 %). Hinweis: Die Aussagen zu __cause__ und raise Neu from Alt stehen in der Quelle im PCPP1-Teil (1.9 Erweiterte Exceptions), der Fall raise "text" ergibt TypeError und der RuntimeError für raise ohne aktive Exception stehen nicht in der Quelle, wurden aber mit Python 3.13 ausgeführt (bitte prüfen, ob sie in der PCAP geprüft werden).