Zum Inhalt springen
aviral gupta

// 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

Anfang des Moduls

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
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Modul B5: Welche dieser Zeilen lösen eine Exception aus? Wählen Sie alle zutreffenden.

    Wählen Sie alle zutreffenden aus.

  2. 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)
  3. Üben · Aufgabe 3 von 7

    Setzen Sie die Basisklasse ein, damit TemperatureError eine gewöhnliche Exception ist, die except Exception abfängt.

    class TemperatureError(____):
        pass
    class TemperatureError():
  4. Ü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")
  5. Üben · Aufgabe 5 von 7

    Auch Pythons eigene Exceptions bilden eine Hierarchie. Ordnen Sie jeder Exception ihre direkte Basisklasse zu.

  6. 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)
  7. 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.py

Ausgabe

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
  1. Hinweis 1

    int(text) löst bei Text wie "ten" ValueError aus. Fangen Sie ihn mit try und except ValueError ab.

  2. Hinweis 2

    Lösen Sie in dieser except-Klausel Ihren eigenen Fehler aus: raise ValidationError(f"not a number: {text!r}").

  3. 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.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.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
  1. Hinweis 1

    Die Basisklasse kommt in die Klammern: class MissingKeyError(ConfigError):. Genauso bei BadValueError.

  2. Hinweis 2

    In describe eine Klausel für die ganze Familie: except ConfigError as err:.

  3. 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.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Häufige Fehler

Eine Exception-Klasse ohne Basisklasse

class StockError:
    pass


raise StockError("no pears left")

Was Python ausgibt

TypeError: StockError() takes no arguments

Warum, 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 full

Warum, 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 defined

Warum, 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.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Eine eigene Exception-Klasse

class PaymentError(Exception): pass erzeugt einen neuen Exception-Typ. In den Klammern steht die Basisklasse: PaymentError ist eine Art Exception, alles Bekannte funktioniert also weiter. raise PaymentError("card declined") löst sie aus, except PaymentError as err: fängt sie ab, str(err) ist die Meldung. Der Rumpf kann pass oder ein Docstring sein. Klassen allgemein folgen in Modul I2; für eine Exception-Klasse genügen die class-Zeile und pass. Leiten Sie von Exception ab, nicht von BaseException, das auch Abbrüche wie KeyboardInterrupt umfasst. Üblich ist ein Name auf Error. Ein eigener Typ sagt dem Aufrufer genau, was schiefging.

Hierarchien: eine Familie über die Basisklasse abfangen

Eine except-Klausel passt auf ihre Klasse und jede davon abgeleitete, aber nicht umgekehrt. Mit class ShopError(Exception) und class PaymentError(ShopError) fängt except ShopError auch einen PaymentError, except PaymentError aber keinen einfachen ShopError. Geben Sie einem Modul also eine Basisklasse und leiten Sie die speziellen Fehler davon ab: Aufrufer fangen alles aus Ihrem Modul mit einer Klausel oder greifen einen Fall heraus. Python macht es genauso: KeyError und IndexError stammen von LookupError ab, ZeroDivisionError von ArithmeticError.

Speziell zuerst, und nicht breiter als nötig

Python prüft die except-Klauseln von oben und führt die erste passende aus. Steht eine Basisklasse vorn, fängt sie alle Unterklassen, und eine Klausel für eine Unterklasse darunter kommt nie dran. Also: spezielle Klassen zuerst, die Basisklasse zuletzt. Und fangen Sie nur ab, was Sie erwarten: except Exception verschluckt auch eigene Tippfehler und Bugs, etwa einen TypeError durch falsche Daten. raise PaymentError ohne Klammern ist die Kurzform für raise PaymentError(): eine Exception ohne Meldung.

Quellen

Zuletzt geprüft am 29. September 2026