Aufwärmen · Aufgabe 1 von 7
// I1.1 · ca. 30 Min. · Aufbau
Eigene Exceptions definieren und auslösen
Nach dieser Lektion geben Sie Ihrem Programm eigene Exception-Typen, fassen sie unter einer Basisklasse zusammen und fangen genau die Fehler ab, die Sie meinen.
Lektion 1 von 5 in I1 Fehler im Detail
Danach können Sie
- Eine eigene Exception-Klasse von Exception ableiten und mit klarer Meldung auslösen
- Exceptions unter einer Basisklasse bündeln und die ganze Familie mit einer except-Klausel abfangen
- except-Klauseln vom Speziellen zum Allgemeinen ordnen und nicht mehr abfangen als beabsichtigt
Vorhersagen · Aufgabe 2 von 7
Sagen Sie es vorher, bevor Sie weiterlesen: Ausgelöst wird OutOfStockError, die except-Klausel nennt aber ShopError. Was gibt das aus?
class ShopError(Exception): pass class OutOfStockError(ShopError): pass try: raise OutOfStockError("no pears left") except ShopError as err: print("shop problem:", err)Üben · Aufgabe 3 von 7
Setzen Sie die Basisklasse ein, damit TemperatureError eine gewöhnliche Exception ist, die except Exception abfängt.
class TemperatureError(____): passclass TemperatureError():Üben · Aufgabe 4 von 7
ConfigError ist von AppError abgeleitet. Was gibt das aus?
class AppError(Exception): pass class ConfigError(AppError): pass try: raise ConfigError("missing key: port") except AppError: print("app error") except ConfigError: print("config error")Üben · Aufgabe 5 von 7
Auch Pythons eigene Exceptions bilden eine Hierarchie. Ordnen Sie jeder Exception ihre direkte Basisklasse zu.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Zwei Exceptions durchlaufen dieselbe try-Anweisung. Was gibt das aus?
class AppError(Exception): pass class ConfigError(AppError): pass log = [] for exc in [ConfigError("bad port"), AppError("disk full")]: try: raise exc except ConfigError: log.append("config") except AppError as err: log.append(f"app: {err}") print(log)Anwenden · Aufgabe 7 von 7
Mini-Aufgabe. Ein Thermostat akzeptiert Werte von 5 bis 30 Grad. Definieren Sie TemperatureError und zwei Unterklassen, TooColdError und TooHotError. Schreiben Sie check_setting(celsius), das die passende mit Meldung auslöst. Probieren Sie dann 4, 21 und 35 in einer Schleife mit nur einer except-Klausel und geben Sie bei jeder Ablehnung Klassenname und Meldung 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
Eine Bank mit eigenen Fehlern
Ein kleines Bankmodul definiert BankError als Basis seiner Fehler und zwei spezielle. withdraw löst InsufficientFundsError oder AccountLockedError aus, ein unbekanntes Konto den eingebauten KeyError. Die Schleife gibt dem speziellsten Fehler eine eigene Meldung, behandelt alle anderen Bankfehler über die Basisklasse und hält KeyError getrennt.
main.py
class BankError(Exception):
"""Base class for every error this module raises."""
class InsufficientFundsError(BankError):
pass
class AccountLockedError(BankError):
pass
LOCKED = {"bob"}
def withdraw(balances: dict[str, float], name: str, amount: float) -> float:
if name in LOCKED:
raise AccountLockedError(f"{name}'s account is locked")
balance = balances[name] # KeyError for an unknown name
if amount > balance:
raise InsufficientFundsError(f"{name} has {balance:.2f}, needs {amount:.2f}")
balances[name] = balance - amount
return balances[name]
balances = {"ada": 50.0, "bob": 20.0}
requests = [("ada", 20.0), ("ada", 40.0), ("bob", 5.0), ("eve", 1.0)]
for name, amount in requests:
try:
left = withdraw(balances, name, amount)
except InsufficientFundsError as err:
print("declined:", err)
except BankError as err:
print(f"bank error ({type(err).__name__}):", err)
except KeyError:
print("unknown account:", name)
else:
print(f"{name} withdrew {amount:.2f}, {left:.2f} left")
Ausführen mit
python main.pyAusgabe
ada withdrew 20.00, 30.00 left
declined: ada has 30.00, needs 40.00
bank error (AccountLockedError): bob's account is locked
unknown account: eve- Ein Docstring allein ist ein gültiger Klassenrumpf, genau wie pass.
- InsufficientFundsError hat eine eigene Klausel über BankError; andersherum käme sie nie dran.
- except BankError fängt AccountLockedError, und type(err).__name__ nennt trotzdem die echte Klasse.
- KeyError ist kein BankError und braucht deshalb eine eigene Klausel.
Ä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
Ein eigener Validierungsfehler
Definieren Sie ValidationError, abgeleitet von Exception. Schreiben Sie dann validate_age(text), das das Alter als int zurückgibt. Ist text keine ganze Zahl, lösen Sie ValidationError mit der Meldung not a number: und dem Text in repr-Form aus, etwa not a number: 'ten'. Bei einem Alter unter 0 oder über 150 lösen Sie ValidationError("age out of range: 200") aus, mit dem echten Wert.
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
int(text) löst bei Text wie "ten" ValueError aus. Fangen Sie ihn mit try und except ValueError ab.
Hinweis 2
Lösen Sie in dieser except-Klausel Ihren eigenen Fehler aus: raise ValidationError(f"not a number: {text!r}").
Hinweis 3
Prüfen Sie nach der try-Anweisung den Bereich mit if not 0 <= age <= 150: und lösen Sie ValidationError(f"age out of range: {age}") aus.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
class ValidationError(Exception):
pass
def validate_age(text: str) -> int:
try:
age = int(text)
except ValueError:
raise ValidationError(f"not a number: {text!r}")
if not 0 <= age <= 150:
raise ValidationError(f"age out of range: {age}")
return age
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 ValidationError(Exception):
pass
def validate_age(text: str) -> int:
return int(text)
test_main.py
from main import ValidationError, validate_age
def test_is_exception():
"""ValidationError stammt von Exception ab"""
assert issubclass(ValidationError, Exception), "ValidationError muss von Exception abgeleitet sein"
def test_valid():
"""Ein gültiges Alter kommt als int zurück"""
got = validate_age("42")
assert got == 42, f"validate_age('42') lieferte {got!r}, erwartet: 42"
def test_not_a_number():
"""Text, der keine Zahl ist, löst ValidationError aus"""
try:
validate_age("ten")
except ValidationError as err:
assert str(err) == "not a number: 'ten'", f"die Meldung war {str(err)!r}"
except ValueError:
assert False, "validate_age löste ValueError aus; lösen Sie stattdessen ValidationError aus"
else:
assert False, "validate_age('ten') kehrte normal zurück"
def test_out_of_range():
"""200 und -1 liegen außerhalb des Bereichs"""
for text in ["200", "-1"]:
try:
validate_age(text)
except ValidationError as err:
want = f"age out of range: {int(text)}"
assert str(err) == want, f"bei {text!r} war die Meldung {str(err)!r}, erwartet: {want!r}"
else:
assert False, f"validate_age({text!r}) 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
Eine Familie von Konfigurationsfehlern
read_port löst bereits MissingKeyError und BadValueError aus. Machen Sie beide zu Unterklassen von ConfigError. Korrigieren Sie dann describe(settings): Es liefert "port " plus den Port oder, bei jedem ConfigError, "config problem: " plus die Meldung, mit nur einer except-Klausel. Andere Fehler dürfen nicht abgefangen werden.
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
Die Basisklasse kommt in die Klammern: class MissingKeyError(ConfigError):. Genauso bei BadValueError.
Hinweis 2
In describe eine Klausel für die ganze Familie: except ConfigError as err:.
Hinweis 3
Verwenden Sie nicht except Exception: Der letzte Test übergibt eine Zahl statt eines Strings, und dieser AttributeError muss durchgehen.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
class ConfigError(Exception):
pass
class MissingKeyError(ConfigError):
pass
class BadValueError(ConfigError):
pass
def read_port(settings: dict[str, str]) -> int:
if "port" not in settings:
raise MissingKeyError("missing key: port")
text = settings["port"]
if not text.isdigit() or not 1 <= int(text) <= 65535:
raise BadValueError(f"port must be 1-65535, got {text!r}")
return int(text)
def describe(settings: dict[str, str]) -> str:
try:
port = read_port(settings)
except ConfigError as err:
return f"config problem: {err}"
return f"port {port}"
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 ConfigError(Exception):
pass
class MissingKeyError(Exception):
pass
class BadValueError(Exception):
pass
def read_port(settings: dict[str, str]) -> int:
if "port" not in settings:
raise MissingKeyError("missing key: port")
text = settings["port"]
if not text.isdigit() or not 1 <= int(text) <= 65535:
raise BadValueError(f"port must be 1-65535, got {text!r}")
return int(text)
def describe(settings: dict[str, str]) -> str:
try:
port = read_port(settings)
except MissingKeyError as err:
return f"config problem: {err}"
return f"port {port}"
test_main.py
from main import BadValueError, ConfigError, MissingKeyError, describe
def test_hierarchy():
"""Beide Fehler sind Unterklassen von ConfigError"""
assert issubclass(MissingKeyError, ConfigError), "MissingKeyError muss von ConfigError abgeleitet sein"
assert issubclass(BadValueError, ConfigError), "BadValueError muss von ConfigError abgeleitet sein"
def test_port():
"""Ein gültiger Port wird beschrieben"""
got = describe({"port": "8080"})
assert got == "port 8080", f"describe lieferte {got!r}"
def test_missing():
"""Ein fehlender Port ist ein Konfigurationsproblem"""
got = describe({})
assert got == "config problem: missing key: port", f"describe lieferte {got!r}"
def test_bad_value():
"""Ein ungültiger Port ist ebenfalls ein Konfigurationsproblem"""
got = describe({"port": "99999"})
assert got == "config problem: port must be 1-65535, got '99999'", f"describe lieferte {got!r}"
def test_other_errors_pass():
"""Fehler, die keine ConfigErrors sind, werden nicht abgefangen"""
try:
describe({"port": 8080}) # type: ignore[dict-item]
except AttributeError:
pass
else:
assert False, "describe fing einen AttributeError ab; fangen Sie nur ConfigError ab"
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
Eine Exception-Klasse ohne Basisklasse
class StockError:
pass
raise StockError("no pears left")
Was Python ausgibt
TypeError: StockError() takes no argumentsWarum, und die Lösung
Ohne Basisklasse ist StockError eine gewöhnliche Klasse, keine Exception. Sie nimmt nicht einmal eine Meldung an, und raise würde sie ebenfalls ablehnen. Nennen Sie die Basisklasse: class StockError(Exception):. Dann nimmt sie eine Meldung an, und raise und except funktionieren wie bei jeder eingebauten Exception.
Erwarten, dass die Klausel einer Unterklasse die Basisklasse fängt
class AppError(Exception):
pass
class ConfigError(AppError):
pass
try:
raise AppError("disk full")
except ConfigError:
print("config problem")
Was Python ausgibt
AppError: disk fullWarum, und die Lösung
except ConfigError fängt ConfigError und davon abgeleitete Klassen. AppError ist seine Basisklasse, keine Unterklasse, also geht der Fehler ungefangen durch. Um jeden Fehler der Familie zu behandeln, fangen Sie die Basisklasse ab: except AppError:.
Eine Basisklasse benutzen, bevor sie definiert ist
class ConfigError(AppError):
pass
class AppError(Exception):
pass
Was Python ausgibt
NameError: name 'AppError' is not definedWarum, und die Lösung
Eine class-Anweisung läuft wie jede andere Zeile von oben nach unten, und die Basisklasse in den Klammern muss schon existieren. Definieren Sie zuerst die Basisklasse, dann die davon abgeleiteten Klassen.
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.