Aufwärmen · Aufgabe 1 von 7
// I1.2 · ca. 30 Min. · Aufbau
Verkettete Exceptions
Nach dieser Lektion lesen Sie einen Traceback mit zwei Exceptions, übersetzen einen technischen Fehler in Ihren eigenen, ohne ihn zu verlieren, und blenden einen nutzlosen Ursprungsfehler aus.
Danach können Sie
- Einen verketteten Traceback lesen und "During handling" von "direct cause" unterscheiden
- Einen Fehler mit raise … from err übersetzen und das Original in __cause__ und __context__ finden
- Einen wenig hilfreichen Ursprungsfehler mit from None ausblenden, obwohl er erhalten bleibt
Vorhersagen · Aufgabe 2 von 7
Sagen Sie es vorher, bevor Sie weiterlesen: Das endet mit einem Traceback, der zwei Exceptions zeigt. Welche Zeile verbindet sie?
settings = {} try: port = settings["port"] except KeyError: raise RuntimeError("config has no port")Üben · Aufgabe 3 von 7
Setzen Sie das Schlüsselwort ein, das den KeyError zur direkten Ursache des ConfigError macht.
raise ConfigError("port is missing") ____ errraise ConfigError("port is missing") errÜben · Aufgabe 4 von 7
Was gibt das aus?
try: try: 1 / 0 except ZeroDivisionError as err: raise ValueError("bad ratio") from err except ValueError as exc: print(type(exc.__cause__).__name__, type(exc.__context__).__name__)Üben · Aufgabe 5 von 7
Ein ValueError wird in except KeyError ausgelöst. Ordnen Sie jedem raise zu, was sein Traceback zeigt.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. from None blendet den KeyError aus. Ist er weg? Was gibt das aus?
def lookup(prices, item): try: return prices[item] except KeyError: raise LookupError(f"no price for {item}") from None try: lookup({}, "tea") except LookupError as err: print(err.__cause__, repr(err.__context__))Anwenden · Aufgabe 7 von 7
Mini-Aufgabe. Definieren Sie PriceError und schreiben Sie parse_price(text), das float(text) zurückgibt. Scheitert float, lösen Sie PriceError mit der Meldung bad price: und dem Text in repr-Form aus, mit from an den ValueError gekettet. Rufen Sie es mit "2.50" und "two" auf und geben Sie beim Fehler die Meldung und repr(err.__cause__) 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
Einstellungen laden: übersetzen, ausblenden oder durchlassen
load_port liest einen Port aus JSON-Text. Ungültiges JSON übersetzt es mit from err in SettingsError, sodass der Parse-Fehler als Ursache sichtbar bleibt. Ein fehlender Schlüssel wird mit from None zu SettingsError, weil der KeyError nichts beiträgt. Ein Port, der keine Zahl ist, wird gar nicht behandelt: Sein ValueError erreicht den Aufrufer unverändert.
main.py
import json
class SettingsError(Exception):
pass
def load_port(text: str) -> int:
try:
data = json.loads(text)
except json.JSONDecodeError as err:
raise SettingsError("settings are not valid JSON") from err
try:
return int(data["port"])
except KeyError:
raise SettingsError("settings have no port") from None
for text in ['{"port": "8080"}', '{"port": 80', '{"host": "a"}', '{"port": "http"}']:
try:
port = load_port(text)
except SettingsError as err:
cause = type(err.__cause__).__name__
context = type(err.__context__).__name__
print(f"{err} | cause: {cause} | context: {context}")
except ValueError as err:
print("not a SettingsError:", err)
else:
print("port", port)
Ausführen mit
python main.pyAusgabe
port 8080
settings are not valid JSON | cause: JSONDecodeError | context: JSONDecodeError
settings have no port | cause: NoneType | context: KeyError
not a SettingsError: invalid literal for int() with base 10: 'http'- Mit from err sind __cause__ und __context__ beide der JSONDecodeError.
- Mit from None ist __cause__ None, aber der KeyError steckt weiter in __context__.
- except KeyError fängt den ValueError aus int("http") nicht ab, er geht unübersetzt durch.
- Der Aufrufer fängt SettingsError ab und muss nichts über json oder KeyError wissen.
Ä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
Die Ursache behalten
parse_price(text) löst bereits PriceError aus, wenn float den Text nicht lesen kann, und bei einem negativen Preis. Ändern Sie es so, dass der "bad price"-PriceError mit from an den ValueError gekettet ist, sodass err.__cause__ dieser ValueError ist. Der Fehler für negative Preise hat keine Ursache und bleibt, wie er ist.
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
Um den ValueError zu verwenden, geben Sie ihm einen Namen: except ValueError as err:.
Hinweis 2
Ergänzen Sie dann from err am Ende der raise-Zeile.
Hinweis 3
raise PriceError(f"bad price: {text!r}") from err
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
class PriceError(Exception):
pass
def parse_price(text: str) -> float:
try:
value = float(text)
except ValueError as err:
raise PriceError(f"bad price: {text!r}") from err
if value < 0:
raise PriceError(f"negative price: {value}")
return value
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
class PriceError(Exception):
pass
def parse_price(text: str) -> float:
try:
value = float(text)
except ValueError:
raise PriceError(f"bad price: {text!r}")
if value < 0:
raise PriceError(f"negative price: {value}")
return value
test_main.py
from main import PriceError, parse_price
def test_valid():
"""Ein gültiger Preis kommt als float zurück"""
got = parse_price("2.50")
assert got == 2.5, f"parse_price('2.50') lieferte {got!r}"
def test_bad_price_message():
"""Text, der keine Zahl ist, löst PriceError mit klarer Meldung aus"""
try:
parse_price("two")
except PriceError as err:
assert str(err) == "bad price: 'two'", f"die Meldung war {str(err)!r}"
else:
assert False, "parse_price('two') kehrte normal zurück"
def test_bad_price_cause():
"""Der ValueError ist die direkte Ursache"""
try:
parse_price("two")
except PriceError as err:
assert isinstance(err.__cause__, ValueError), f"__cause__ ist {err.__cause__!r}; verwenden Sie raise ... from err"
def test_negative():
"""Ein negativer Preis löst PriceError ohne Ursache aus"""
try:
parse_price("-1")
except PriceError as err:
assert str(err) == "negative price: -1.0", f"die Meldung war {str(err)!r}"
assert err.__cause__ is None, f"__cause__ ist {err.__cause__!r}, erwartet: None"
else:
assert False, "parse_price('-1') kehrte normal zurück"
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
Den Zugriff ausblenden, die Umwandlung behalten
Schreiben Sie get_setting(settings, key). Es liefert settings[key]. Fehlt der Schlüssel, lösen Sie SettingError(f"unknown setting: {key!r}") from None aus. Schreiben Sie dann get_int_setting(settings, key), das int(get_setting(settings, key)) liefert. Scheitert int, lösen Sie SettingError(f"setting {key!r} is not a whole number") aus, mit from an den ValueError gekettet.
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
Setzen Sie in get_setting return settings[key] in ein try und schreiben Sie in except KeyError: raise SettingError(...) from None.
Hinweis 2
Rufen Sie in get_int_setting get_setting außerhalb des try auf, damit sein SettingError nicht erneut abgefangen wird. Nur int(text) gehört ins try.
Hinweis 3
except ValueError as err: raise SettingError(f"setting {key!r} is not a whole number") from err
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
class SettingError(Exception):
pass
def get_setting(settings: dict[str, str], key: str) -> str:
try:
return settings[key]
except KeyError:
raise SettingError(f"unknown setting: {key!r}") from None
def get_int_setting(settings: dict[str, str], key: str) -> int:
text = get_setting(settings, key)
try:
return int(text)
except ValueError as err:
raise SettingError(f"setting {key!r} is not a whole number") from err
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
class SettingError(Exception):
pass
def get_setting(settings: dict[str, str], key: str) -> str:
return settings[key]
def get_int_setting(settings: dict[str, str], key: str) -> int:
return int(get_setting(settings, key))
test_main.py
from main import SettingError, get_int_setting, get_setting
SETTINGS = {"port": "8080", "host": "example.org"}
def catch(f, key):
try:
f(SETTINGS, key)
except SettingError as err:
return err
assert False, f"{f.__name__}(settings, {key!r}) kehrte normal zurück"
def test_values():
"""Vorhandene Einstellungen kommen zurück"""
got = get_setting(SETTINGS, "host"), get_int_setting(SETTINGS, "port")
assert got == ("example.org", 8080), f"Ergebnis: {got!r}"
def test_unknown_hidden():
"""Ein unbekannter Schlüssel löst SettingError from None aus"""
err = catch(get_setting, "user")
assert str(err) == "unknown setting: 'user'", f"die Meldung war {str(err)!r}"
assert err.__cause__ is None and err.__suppress_context__, "der KeyError sollte mit from None ausgeblendet werden"
assert isinstance(err.__context__, KeyError), f"__context__ ist {err.__context__!r}, erwartet: der KeyError"
def test_not_a_number_chained():
"""Eine Einstellung, die keine Zahl ist, wird an den ValueError gekettet"""
err = catch(get_int_setting, "host")
assert str(err) == "setting 'host' is not a whole number", f"die Meldung war {str(err)!r}"
assert isinstance(err.__cause__, ValueError), f"__cause__ ist {err.__cause__!r}; verwenden Sie raise ... from err"
def test_unknown_int():
"""get_int_setting meldet einen unbekannten Schlüssel genauso"""
err = catch(get_int_setting, "user")
assert str(err) == "unknown setting: 'user'", f"die Meldung war {str(err)!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 herunterladenHäufige Fehler
Ein String nach from
raise RuntimeError("could not load") from "file missing"
Was Python ausgibt
TypeError: exception causes must derive from BaseExceptionWarum, und die Lösung
Die Ursache muss ein Exception-Objekt sein (oder None), keine Meldung. Fangen Sie das Original mit except OSError as err: ab und schreiben Sie raise RuntimeError("could not load") from err. Zusätzliche Worte gehören in die Meldung der neuen Exception.
Den as-Namen nach der except-Klausel benutzen
try:
int("x")
except ValueError as err:
print("bad number")
print(err)
Was Python ausgibt
NameError: name 'err' is not definedWarum, und die Lösung
Python löscht den Namen nach as, sobald die except-Klausel endet. Wollen Sie die Exception später verwenden, etwa zum Verketten oder Melden, weisen Sie sie in der Klausel einer anderen Variablen zu: saved = err.
Ein Fehler im Handler selbst
prices = {"tea": 2.5}
try:
price = prices["coffee"]
except KeyError:
price = fallbak_price
Was Python ausgibt
NameError: name 'fallbak_price' is not definedWarum, und die Lösung
Der Traceback zeigt den KeyError, dann During handling of the above exception, another exception occurred:, dann diesen NameError. Diese Verbindungszeile heißt: Der Handler selbst ist gescheitert, hier an einem falsch geschriebenen Namen. Korrigieren Sie den Handler, nicht den try-Block.
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.