W3docs

Python raise und benutzerdefinierte Ausnahmen

Python raise-Anweisung, Exception Chaining mit raise...from und eigene Exception-Klassen für übersichtliche Fehlerbehandlung erklärt.

Python erlaubt mehr als das Abfangen von Fehlern — Sie können sie auch gezielt mit der raise-Anweisung signalisieren und eigene Ausnahmetypen erstellen, um domänenspezifische Probleme darzustellen. Dieses Kapitel baut auf Python Try...Except auf und behandelt:

  • Die raise-Anweisung — eingebaute Ausnahmen auslösen
  • Ausnahmen innerhalb eines except-Blocks erneut auslösen
  • Exception Chaining mit raise ... from
  • Benutzerdefinierte Exception-Klassen erstellen
  • Eine Exception-Hierarchie für eine reale Anwendung aufbauen
  • Die assert-Anweisung und wann man sie einsetzt

Die raise-Anweisung

Die raise-Anweisung ermöglicht es Ihnen, an jeder Stelle Ihres Codes eine Ausnahme auszulösen. Die gebräuchlichste Form übergibt eine Exception-Instanz mit einer beschreibenden Meldung:

raise ExceptionType("message")

Verwenden Sie raise, wenn Ihr Code ein Problem erkennt, das der Aufrufer behandeln muss. Eine Funktion, die ein Alter entgegennimmt, sollte zum Beispiel negative Werte sofort ablehnen, anstatt stillschweigend fortzufahren:

def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    return age

try:
    set_age(-1)
except ValueError as e:
    print(e)
# Output: Age cannot be negative

Die richtige eingebaute Ausnahme wählen

Pythons eingebaute Ausnahmetypen tragen Bedeutung. Die richtige Wahl macht Ihre API verständlicher und ermöglicht es Aufrufern, verschiedene Fehlerkategorien separat zu behandeln.

AusnahmeWann auslösen
ValueErrorArgument hat den richtigen Typ, aber einen ungültigen Wert (age = -1)
TypeErrorArgument hat den falschen Typ (age = "old")
KeyErrorEin erforderlicher Dictionary-Schlüssel fehlt
IndexErrorEin Sequenzindex liegt außerhalb des gültigen Bereichs
FileNotFoundErrorEine erforderliche Datei existiert nicht
PermissionErrorDer Prozess hat keine Rechte zur Durchführung einer Operation
RuntimeErrorEin allgemeines Laufzeitproblem, das keinen spezifischeren Typ hat
NotImplementedErrorEine Methode existiert in einer Basisklasse, muss aber überschrieben werden

Das Auslösen von ValueError für einen falschen Wert ist viel aussagekräftiger als das Auslösen einer bloßen Exception, weil Aufrufer except ValueError schreiben können, um genau diesen Fall zu behandeln.

Eine Ausnahme erneut auslösen

Manchmal möchten Sie bei einer Ausnahme etwas tun — sie protokollieren, eine Ressource bereinigen — und dann dieselbe Ausnahme unverändert an den Aufrufer weitergeben. Rufen Sie raise ohne Argumente innerhalb eines except-Blocks auf, um die aktuelle Ausnahme erneut auszulösen:

def read_config(path):
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError:
        print(f"Warning: config file not found at {path}")
        raise  # re-raise the original FileNotFoundError

try:
    read_config("missing.cfg")
except FileNotFoundError as e:
    print(f"Caught: {e}")
# Output:
# Warning: config file not found at missing.cfg
# Caught: [Errno 2] No such file or directory: 'missing.cfg'

Das bloße raise bewahrt den ursprünglichen Traceback, was das Debuggen wesentlich einfacher macht, als e als neue Ausnahme abzufangen und erneut auszulösen.

Exception Chaining mit raise ... from

Wenn Sie eine Ausnahme abfangen und eine andere auslösen, zeichnet Python die ursprüngliche Ausnahme automatisch als Kontext der neuen auf. Sie können diese Beziehung explizit und bedeutungsvoll machen, indem Sie raise NewException from original verwenden:

def load_data(path):
    try:
        with open(path) as f:
            return f.read()
    except OSError as e:
        raise RuntimeError("Failed to load configuration") from e

try:
    load_data("config.json")
except RuntimeError as e:
    print(f"Error: {e}")
    print(f"Caused by: {e.__cause__}")
# Output:
# Error: Failed to load configuration
# Caused by: [Errno 2] No such file or directory: 'config.json'

Wenn Python den Traceback ausgibt, zeigt es beide Ausnahmen der Reihe nach an und macht deutlich, dass der RuntimeError eine direkte Folge des OSError war. Dies ist besonders nützlich in Bibliothekscode, wo Sie Low-Level-OS-Fehler in Higher-Level-Domänenfehler übersetzen möchten, ohne die Ursache zu verbergen.

Die Kette mit raise ... from None unterdrücken

Gelegentlich ist die ursprüngliche Ausnahme ein Implementierungsdetail, das Sie nicht offenlegen möchten. Übergeben Sie None als Ursache, um sie zu verbergen:

def fetch(url):
    try:
        raise ConnectionError("timeout")
    except ConnectionError:
        raise RuntimeError("Network unavailable") from None

try:
    fetch("http://example.com")
except RuntimeError as e:
    print(f"Error: {e}")
    print(f"Cause hidden: {e.__cause__}")
# Output:
# Error: Network unavailable
# Cause hidden: None

Der Traceback zeigt nur den RuntimeError. Verwenden Sie dies sparsam — das Verbergen der Ursache erschwert das Debuggen für Bibliotheksnutzer.

Benutzerdefinierte Exception-Klassen erstellen

Eingebaute Ausnahmen decken häufige Programmierfehler ab, sind aber für Domänenprobleme zu allgemein. Wenn Ihre E-Commerce-Anwendung einen einfachen ValueError auslöst, wenn eine Zahlung fehlschlägt, können Aufrufer das nicht von einem ungültigen Funktionsargument unterscheiden. Benutzerdefinierte Exception-Klassen lösen dieses Problem.

Eine benutzerdefinierte Ausnahme ist einfach eine Klasse, die von Exception (oder einer ihrer Unterklassen) erbt:

class InsufficientFundsError(Exception):
    """Raised when a bank account has insufficient funds."""
    def __init__(self, amount, balance):
        self.amount = amount
        self.balance = balance
        super().__init__(
            f"Cannot withdraw {amount}: balance is only {balance}"
        )

class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(amount, self.balance)
        self.balance -= amount
        return self.balance

account = BankAccount(100)
try:
    account.withdraw(150)
except InsufficientFundsError as e:
    print(e)
    print(f"You tried to withdraw: {e.amount}")
    print(f"Available balance:     {e.balance}")
# Output:
# Cannot withdraw 150: balance is only 100
# You tried to withdraw: 150
# Available balance:     100

Wichtige Punkte zu diesem Muster:

  • super().__init__(message) setzt den menschenlesbaren string, der von str(e) zurückgegeben wird.
  • Zusätzliche Attribute (self.amount, self.balance) ermöglichen es Aufrufern, strukturierte Daten aus der Ausnahme abzurufen, nicht nur einen string.
  • Ein klarer Docstring dokumentiert, wann die Ausnahme ausgelöst werden soll.

Eine Exception-Hierarchie aufbauen

Reale Anwendungen haben oft viele verwandte Fehlertypen. Wenn man sie unter einer gemeinsamen Basisklasse gruppiert, können Aufrufer entweder den spezifischen Fehler oder die gesamte Kategorie abfangen:

class AppError(Exception):
    """Base class for all application errors."""

class ValidationError(AppError):
    """Raised when user input fails validation."""

class DatabaseError(AppError):
    """Raised when a database operation fails."""

def validate_username(name):
    if len(name) < 3:
        raise ValidationError(f"Username '{name}' is too short (min 3 chars)")

try:
    validate_username("ab")
except ValidationError as e:
    print(f"Validation failed: {e}")
except AppError as e:
    print(f"Application error: {e}")
# Output:
# Validation failed: Username 'ab' is too short (min 3 chars)

Ein Aufrufer, der nur Datenbankfehler abfangen möchte, kann except DatabaseError schreiben. Ein Aufrufer, der jedes Problem aus Ihrer Bibliothek abfangen möchte, kann except AppError schreiben. Dies spiegelt das Design von Pythons eigener Exception-Hierarchie wider, wo OSError die Klassen FileNotFoundError, PermissionError und weitere zusammenfasst.

Richtlinien für benutzerdefinierte Ausnahmen

  • Von Exception erben, nicht von BaseException. BaseException ist die Wurzel von Pythons Hierarchie und schließt auch SystemExit und KeyboardInterrupt ein, die nicht versehentlich abgefangen werden sollten.
  • Den Klassennamen auf Error enden lassen für Ausnahmen, die ein Problem signalisieren. Dies entspricht Pythons eigenem Benennungsschema (ValueError, TypeError, IOError).
  • Die Klasse minimal halten, sofern keine zusätzlichen Attribute benötigt werden. Ein leerer Rumpf mit einem Docstring ist vollkommen gültig.
  • Ausnahmen in einem eigenen Modul ablegen (z. B. exceptions.py) bei größeren Projekten, damit Aufrufer sie importieren können, ohne den Rest Ihres Codes einzubinden.

Die assert-Anweisung

assert ist eine leichtgewichtige Möglichkeit, Invarianten auszudrücken — Bedingungen, die wahr sein müssen, damit Ihr Code korrekt ist:

def divide(a, b):
    assert b != 0, "Divisor must not be zero"
    return a / b

try:
    divide(10, 0)
except AssertionError as e:
    print(f"AssertionError: {e}")

print(divide(10, 2))
# Output:
# AssertionError: Divisor must not be zero
# 5.0

assert condition, message löst AssertionError mit der angegebenen Meldung aus, wenn condition False ist.

Wichtige Einschränkung: Python entfernt assert-Anweisungen, wenn es mit dem Flag -O (Optimierung) ausgeführt wird. Das bedeutet:

  • Verwenden Sie assert nur für interne Konsistenzprüfungen und Debugging-Hilfen.
  • Verwenden Sie raise mit einer geeigneten Ausnahme für benutzerseitige Eingabevalidierung und öffentliche API-Prüfungen, die immer ausgeführt werden müssen.

Häufige Fehler

Ausnahmen abfangen und stillschweigend ignorieren

# Bad — the error disappears
try:
    result = risky_operation()
except Exception:
    pass

# Better — at minimum, log or re-raise
try:
    result = risky_operation()
except Exception as e:
    print(f"Operation failed: {e}")
    raise

Einen string statt einer Ausnahme auslösen

# Wrong — strings are not exceptions
raise "something went wrong"  # TypeError

# Correct
raise ValueError("something went wrong")

BaseException versehentlich abfangen

# Dangerous — this catches KeyboardInterrupt and SystemExit too
except BaseException:
    ...

# Use Exception instead
except Exception:
    ...

Zusammenfassung

TechnikWann einsetzen
raise ExceptionType("msg")Ein Problem signalisieren, das der Aufrufer behandeln muss
raise (ohne Argument)Die aktuelle Ausnahme nach Protokollierung oder Bereinigung erneut auslösen
raise NewError(...) from originalEinen Low-Level-Fehler in einen Higher-Level-Fehler übersetzen und die Ursache erhalten
raise NewError(...) from NoneEinen Fehler übersetzen und die interne Ursache verbergen
Benutzerdefinierte Exception-KlasseDomänenspezifischen Fehlern einen eindeutigen, abfangbaren Typ geben
Exception-HierarchieAufrufern ermöglichen, enge oder breite Fehlerkategorien abzufangen
assertInterne Invarianten nur während der Entwicklung prüfen

Für das vollständige Bild zum Abfangen und Behandeln von Ausnahmen siehe Python Try...Except. Um zu verstehen, wie benutzerdefinierte Ausnahmen in das Klassendesign passen, lesen Sie Python Classes and Objects und Python Inheritance.

Übungen

Übung
Which statement correctly raises a ValueError with the message 'invalid input'?
Which statement correctly raises a ValueError with the message 'invalid input'?
Übung
What does bare raise (with no argument) do inside an except block?
What does bare raise (with no argument) do inside an except block?
Übung
Which base class should a custom exception inherit from?
Which base class should a custom exception inherit from?
Was this page helpful?