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 negativeDie 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.
| Ausnahme | Wann auslösen |
|---|---|
ValueError | Argument hat den richtigen Typ, aber einen ungültigen Wert (age = -1) |
TypeError | Argument hat den falschen Typ (age = "old") |
KeyError | Ein erforderlicher Dictionary-Schlüssel fehlt |
IndexError | Ein Sequenzindex liegt außerhalb des gültigen Bereichs |
FileNotFoundError | Eine erforderliche Datei existiert nicht |
PermissionError | Der Prozess hat keine Rechte zur Durchführung einer Operation |
RuntimeError | Ein allgemeines Laufzeitproblem, das keinen spezifischeren Typ hat |
NotImplementedError | Eine 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: NoneDer 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: 100Wichtige Punkte zu diesem Muster:
super().__init__(message)setzt den menschenlesbaren string, der vonstr(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
Exceptionerben, nicht vonBaseException.BaseExceptionist die Wurzel von Pythons Hierarchie und schließt auchSystemExitundKeyboardInterruptein, die nicht versehentlich abgefangen werden sollten. - Den Klassennamen auf
Errorenden 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.0assert 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
assertnur für interne Konsistenzprüfungen und Debugging-Hilfen. - Verwenden Sie
raisemit 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}")
raiseEinen 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
| Technik | Wann 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 original | Einen Low-Level-Fehler in einen Higher-Level-Fehler übersetzen und die Ursache erhalten |
raise NewError(...) from None | Einen Fehler übersetzen und die interne Ursache verbergen |
| Benutzerdefinierte Exception-Klasse | Domänenspezifischen Fehlern einen eindeutigen, abfangbaren Typ geben |
| Exception-Hierarchie | Aufrufern ermöglichen, enge oder breite Fehlerkategorien abzufangen |
assert | Interne 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.