Python custom exceptions โ how and when to create them
custom exception class, exception hierarchy, __init__ override, contextual error messages, re-raising exceptions, exception chaining
Custom Exceptions
Custom exceptions make your error handling expressive and catchable by callers. Subclass Exception (not BaseException). Keep them specific.
class AppError(Exception):
"""Base class for all application errors."""
class ValidationError(AppError):
def __init__(self, field, message):
self.field = field
super().__init__(f"Validation failed on '{field}': {message}")
class NotFoundError(AppError):
def __init__(self, resource, identifier):
super().__init__(f"{resource} '{identifier}' not found")
# Usage
try:
raise ValidationError("email", "invalid format")
except ValidationError as e:
print(e) # Validation failed on 'email': invalid format
print(e.field) # email
except AppError:
print("generic app error")
Exception Chaining
try:
int("abc")
except ValueError as e:
raise RuntimeError("Conversion failed") from e
# RuntimeError shows original ValueError as cause
Use raise X from Y to explicitly chain exceptions โ it preserves the original traceback and makes debugging much easier.
A well-designed exception hierarchy makes calling code easier to write. Callers can catch the specific exception for precise handling or catch the base application error for a broad fallback. Document your custom exceptions in the module's docstring or README so API consumers know what to expect. Avoid creating one custom exception per function โ group related errors under a meaningful base class. Exception messages should say what went wrong and ideally what the caller can do about it, not just repeat the exception class name.
