Aufwärmen · Aufgabe 1 von 7
// I1.4 · ca. 35 Min. · Aufbau
Exception-Gruppen und Notizen
Nach dieser Lektion sammeln Sie mehrere Fehler und lösen sie gemeinsam aus, behandeln jede Art mit except* und hängen Notizen an, die sagen, wo ein Fehler passiert ist.
Danach können Sie
- Fehler in einer Schleife sammeln, als ExceptionGroup auslösen und ihren Traceback lesen
- Teile einer Gruppe mit except* behandeln und vorhersagen, welche Klauseln laufen und was übrig bleibt
- Einer Exception mit add_note Kontext geben und die Notizen in __notes__ und im Traceback finden
Vorhersagen · Aufgabe 2 von 7
Sagen Sie es vorher, bevor Sie weiterlesen: Das hier löst zwei Fehler gemeinsam aus. Eine Zeile des Tracebacks beschreibt die Gruppe selbst. Welche?
errors = [ValueError("age must be a number"), KeyError("email")] raise ExceptionGroup("2 records failed", errors)Üben · Aufgabe 3 von 7
Setzen Sie die Klasse ein, die alle Fehler der Liste gemeinsam auslöst.
raise ____("3 rows failed", errors)raise ("3 rows failed", errors)Üben · Aufgabe 4 von 7
Eine Gruppe, zwei except*-Klauseln. Was gibt das aus?
try: raise ExceptionGroup("batch", [ValueError("a"), KeyError("b"), ValueError("c")]) except* ValueError as eg: print("values:", len(eg.exceptions), end="; ") except* KeyError as eg: print("keys:", len(eg.exceptions), end="; ")Üben · Aufgabe 5 von 7
eg = ExceptionGroup("batch", [ValueError("a"), KeyError("b")]). Ordnen Sie jedem Ausdruck seinen Wert zu.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Nur der ValueError hat eine except*-Klausel. Was gibt das aus?
try: try: raise ExceptionGroup("batch", [ValueError("a"), KeyError("b")]) except* ValueError: print("handled the ValueError", end=" | ") except ExceptionGroup as eg: print("left over:", eg.exceptions)Anwenden · Aufgabe 7 von 7
Mini-Aufgabe. Schreiben Sie check_prices(prices); prices ordnet einem Namen einen Text wie "2.5" zu. Wandeln Sie jeden mit float um und lehnen Sie negative Preise ab. Hören Sie nicht beim ersten Problem auf: Geben Sie jedem Fehler eine Notiz mit dem Namen, sammeln Sie sie und lösen Sie am Ende eine ExceptionGroup aus. Behandeln Sie sie mit except* ValueError und geben Sie jede Meldung mit ihrer Notiz aus.
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Selbst programmieren
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Einen Stapel Anmeldungen prüfen
check_all prüft jede Anmeldung und hört nicht bei der ersten schlechten auf. Jeder Fehler bekommt eine Notiz mit seiner Datensatznummer und landet in einer Liste, und am Ende trägt eine ExceptionGroup sie alle. Der Aufrufer behandelt falsche Werte und fehlende Felder in getrennten except*-Klauseln und gibt jeden Fehler mit seiner Notiz aus.
main.py
def check(record: dict[str, str]) -> None:
if "email" not in record:
raise KeyError("email")
if not record["age"].isdigit():
raise ValueError(f"age must be a number, got {record['age']!r}")
def check_all(records: list[dict[str, str]]) -> None:
errors: list[Exception] = []
for number, record in enumerate(records, start=1):
try:
check(record)
except (ValueError, KeyError) as err:
err.add_note(f"in record {number}")
errors.append(err)
if errors:
raise ExceptionGroup(f"{len(errors)} of {len(records)} records failed", errors)
records = [
{"age": "34", "email": "ada@example.org"},
{"age": "x", "email": "bob@example.org"},
{"age": "51"},
{"age": "-3", "email": "eve@example.org"},
]
try:
check_all(records)
except* ValueError as bad_values:
print(bad_values.message)
for err in bad_values.exceptions:
print("bad value:", err, "|", err.__notes__[0])
except* KeyError as missing:
for key_err in missing.exceptions:
print("missing field:", key_err, "|", key_err.__notes__[0])
Ausführen mit
python main.pyAusgabe
3 of 4 records failed
bad value: age must be a number, got 'x' | in record 2
bad value: age must be a number, got '-3' | in record 4
missing field: 'email' | in record 3- Die Meldung der Gruppe bleibt in der kleineren Gruppe erhalten, die jede except*-Klausel bekommt.
- Beide ValueErrors kommen zusammen in einem Durchlauf der ersten except*-Klausel an.
- Nachdem die ValueErrors behandelt sind, bleibt der KeyError übrig, also läuft auch die zweite Klausel.
- str(err) ist nur die Meldung; die Notiz steht in err.__notes__.
Ändern und ausführen
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Übungen
Übung 1 von 2
Sammeln statt abbrechen
parse_all(texts) wandelt eine Liste von Texten in ints um, bricht aber beim ersten schlechten ab. Lassen Sie die Funktion jeden Text prüfen: Geben Sie jedem ValueError die Notiz "item <n>", gezählt ab 1, sammeln Sie sie und lösen Sie nach der Schleife ExceptionGroup("some items are not numbers", errors) aus. Sind alle Texte in Ordnung, geben Sie die Liste der ints zurück.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Gehen Sie mit enumerate(texts, start=1) durch die Liste und setzen Sie int(text) in ein try innerhalb der Schleife.
Hinweis 2
In except ValueError as err: rufen Sie err.add_note(f"item {n}") auf und hängen err an eine Fehlerliste.
Hinweis 3
Nach der Schleife: if errors: raise ExceptionGroup("some items are not numbers", errors). Sonst geben Sie die Zahlen zurück.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
def parse_all(texts: list[str]) -> list[int]:
numbers: list[int] = []
errors: list[ValueError] = []
for n, text in enumerate(texts, start=1):
try:
numbers.append(int(text))
except ValueError as err:
err.add_note(f"item {n}")
errors.append(err)
if errors:
raise ExceptionGroup("some items are not numbers", errors)
return numbers
Auf dem eigenen Computer ausführen
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
def parse_all(texts: list[str]) -> list[int]:
return [int(text) for text in texts]
test_main.py
from main import parse_all
def test_all_good():
"""Gute Texte kommen als ints zurück"""
got = parse_all(["1", "22", "-3"])
assert got == [1, 22, -3], f"parse_all lieferte {got!r}"
def catch(texts):
try:
parse_all(texts)
except ExceptionGroup as eg:
return eg
except ValueError:
assert False, "parse_all brach beim ersten ValueError ab; sammeln Sie sie in einer ExceptionGroup"
assert False, "parse_all kehrte bei schlechter Eingabe normal zurück"
def test_group():
"""Zwei schlechte Texte ergeben eine Gruppe mit beiden Fehlern"""
eg = catch(["1", "x", "3", "y"])
assert eg.message == "some items are not numbers", f"die Meldung der Gruppe ist {eg.message!r}"
assert len(eg.exceptions) == 2, f"die Gruppe enthält {len(eg.exceptions)} Exceptions, erwartet: 2"
assert all(isinstance(e, ValueError) for e in eg.exceptions), "jede Exception in der Gruppe sollte ein ValueError sein"
def test_notes():
"""Jeder Fehler hat eine Notiz mit seiner Position"""
eg = catch(["1", "x", "3", "y"])
notes = [getattr(e, "__notes__", []) for e in eg.exceptions]
assert notes == [["item 2"], ["item 4"]], f"die Notizen sind {notes!r}"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 2 von 2
Die Gruppe sortieren
check_all ist fertig: Es löst eine ExceptionGroup aus ValueErrors und KeyErrors aus, jeder mit einer Notiz. Schreiben Sie summarize(records), das eine Liste von Zeilen liefert: "bad value: <message> (<note>)" für jeden ValueError, dann "missing field: <key> (<note>)" für jeden KeyError, mit except*. Löst check_all nichts aus, liefern Sie ["all valid"]. Andere Fehler lassen Sie durch.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Beginnen Sie mit einer leeren Liste lines, dann try: check_all(records) mit zwei except*-Klauseln: except* ValueError as bad_values: und except* KeyError as missing:.
Hinweis 2
Gehen Sie in jeder Klausel die Exceptions der Gruppe durch und hängen Sie einen f-String mit dem Fehler und seinem __notes__[0] an. Nutzen Sie in der zweiten Klausel neue Namen, sonst beschwert sich mypy. return ist in except* verboten.
Hinweis 3
Nach der try-Anweisung: return lines or ["all valid"]. Eine leere Liste ist falsch.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
def check(record: dict[str, str]) -> None:
if "email" not in record:
raise KeyError("email")
if not record["age"].isdigit():
raise ValueError(f"age must be a number, got {record['age']!r}")
def check_all(records: list[dict[str, str]]) -> None:
errors: list[Exception] = []
for number, record in enumerate(records, start=1):
try:
check(record)
except (ValueError, KeyError) as err:
err.add_note(f"in record {number}")
errors.append(err)
if errors:
raise ExceptionGroup(f"{len(errors)} records failed", errors)
def summarize(records: list[dict[str, str]]) -> list[str]:
lines: list[str] = []
try:
check_all(records)
except* ValueError as bad_values:
for err in bad_values.exceptions:
lines.append(f"bad value: {err} ({err.__notes__[0]})")
except* KeyError as missing:
for key_err in missing.exceptions:
lines.append(f"missing field: {key_err} ({key_err.__notes__[0]})")
return lines or ["all valid"]
Auf dem eigenen Computer ausführen
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
def check(record: dict[str, str]) -> None:
if "email" not in record:
raise KeyError("email")
if not record["age"].isdigit():
raise ValueError(f"age must be a number, got {record['age']!r}")
def check_all(records: list[dict[str, str]]) -> None:
errors: list[Exception] = []
for number, record in enumerate(records, start=1):
try:
check(record)
except (ValueError, KeyError) as err:
err.add_note(f"in record {number}")
errors.append(err)
if errors:
raise ExceptionGroup(f"{len(errors)} records failed", errors)
def summarize(records: list[dict[str, str]]) -> list[str]:
check_all(records)
return ["all valid"]
test_main.py
from main import summarize
def test_all_valid():
"""Gültige Datensätze ergeben all valid"""
got = summarize([{"age": "34", "email": "ada@example.org"}])
assert got == ["all valid"], f"summarize lieferte {got!r}"
def test_both_kinds():
"""Erst die falschen Werte, dann die fehlenden Felder, jeweils mit Notiz"""
records = [{"age": "x", "email": "a@b"}, {"age": "3"}, {"age": "-3", "email": "c@d"}]
got = summarize(records)
want = [
"bad value: age must be a number, got 'x' (in record 1)",
"bad value: age must be a number, got '-3' (in record 3)",
"missing field: 'email' (in record 2)",
]
assert got == want, f"summarize lieferte {got!r}"
def test_only_missing():
"""Auch eine Gruppe nur aus KeyErrors wird behandelt"""
got = summarize([{"age": "1"}, {"age": "2"}])
assert got == ["missing field: 'email' (in record 1)", "missing field: 'email' (in record 2)"], f"summarize lieferte {got!r}"
def test_other_errors_pass():
"""Andere Fehler werden nicht gefangen"""
try:
summarize([{"age": 5, "email": "a@b"}]) # type: ignore[dict-item]
except AttributeError:
pass
else:
assert False, "summarize hat einen AttributeError verschluckt"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenHäufige Fehler
except und except* gemischt
try:
raise ExceptionGroup("batch", [ValueError("a")])
except* ValueError:
print("values")
except KeyError:
print("keys")
Was Python ausgibt
SyntaxError: cannot have both 'except' and 'except*' on the same 'try'Warum, und die Lösung
Eine try-Anweisung nutzt nur eine Art Klausel. In einer Gruppe fangen Sie auch den KeyError mit except* KeyError:. Wollen Sie stattdessen die ganze Gruppe auf einmal fangen, nutzen Sie ein einfaches except ExceptionGroup: in einer eigenen try-Anweisung.
except* mit ExceptionGroup
try:
raise ExceptionGroup("batch", [ValueError("a")])
except* ExceptionGroup:
print("caught")
Was Python ausgibt
TypeError: catching ExceptionGroup with except* is not allowed. Use except instead.Warum, und die Lösung
except* schaut sich die Exceptions in einer Gruppe an, der Gruppentyp selbst hat dort keine klare Bedeutung, und Python lehnt ihn ab, sobald die Klausel erreicht wird. Um eine Gruppe als Ganzes zu fangen, schreiben Sie except ExceptionGroup as eg:.
return in except*
def load():
try:
raise ExceptionGroup("batch", [ValueError("a")])
except* ValueError:
return "handled"
Was Python ausgibt
SyntaxError: 'break', 'continue' and 'return' cannot appear in an except* blockWarum, und die Lösung
Nach dieser Klausel müssen eventuell noch weitere except*-Klauseln laufen, deshalb ist vorzeitiges Verlassen verboten. Speichern Sie das Ergebnis in der Klausel in einer Variablen und geben Sie es nach der try-Anweisung zurück.
Eine Notiz, die kein String ist
err = ValueError("bad age")
err.add_note(42)
Was Python ausgibt
TypeError: add_note() argument must be str, not intWarum, und die Lösung
Notizen sind Text, der im Traceback erscheint, also nimmt add_note nur einen String. Bauen Sie ihn mit einem f-String: err.add_note(f"in record {42}").
Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode
Abschlussquiz
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.