Custom Errors
Why custom exceptions matter
Section titled “Why custom exceptions matter”Python’s built-in exceptions (ValueError, TypeError, RuntimeError, …) are generic.
When your code raises a ValueError, callers cannot tell whether it came from their input, a broken config file, or a network response.
Custom exception classes give your errors a distinct type that callers can catch selectively, add structured attributes (like HTTP status codes or field names), and document what can go wrong in each part of your application.
Subclassing Exception
Section titled “Subclassing Exception”The minimal custom exception is just a class that inherits from Exception:
class AppError(Exception): """Base class for all application errors."""That is enough to raise and catch it by name:
raise AppError("something went wrong")Adding custom attributes
Section titled “Adding custom attributes”Override __init__ to attach extra context to the exception:
class AppError(Exception): def __init__(self, message: str, code: int = 0) -> None: super().__init__(message) # pass message to Exception self.code = codeCallers can then read err.code to get structured information without parsing the message string.
Building a hierarchy
Section titled “Building a hierarchy”Create subclasses for each distinct failure category. Every NotFoundError is also an AppError, so callers can catch at whichever level of specificity they need.
class ValidationError(AppError): def __init__(self, field: str, message: str) -> None: super().__init__(f'Validation failed on "{field}": {message}', code=400) self.field = field
class NotFoundError(AppError): def __init__(self, resource: str, resource_id: int) -> None: super().__init__(f"{resource} id={resource_id} not found", code=404) self.resource = resource self.resource_id = resource_idCatching at different levels:
try: raise ValidationError("email", "must contain @")except ValidationError as e: print(f"Field {e.field!r} invalid: {e}") # specificexcept AppError as e: print(f"App error [{e.code}]: {e}") # broadstr(exception) and repr(exception)
Section titled “str(exception) and repr(exception)”str(e) returns the message you passed to super().__init__(). This is what gets printed when you print(e) or include the exception in an f-string.
repr(e) returns ExceptionClass(message) — useful for logging.
Full runnable demo
Section titled “Full runnable demo”# --- base application error ---class AppError(Exception): """Base class for all application errors.""" def __init__(self, message: str, code: int = 0) -> None: super().__init__(message) self.code = code
# --- specific subclasses ---class ValidationError(AppError): """Raised when input validation fails.""" def __init__(self, field: str, message: str) -> None: super().__init__( f'Validation failed on field "{field}": {message}', code=400, ) self.field = field
class NotFoundError(AppError): """Raised when a requested resource does not exist.""" def __init__(self, resource: str, resource_id: int) -> None: super().__init__( f"{resource} with id={resource_id} not found", code=404, ) self.resource = resource self.resource_id = resource_id
# --- catching at different levels ---errors = [ ValidationError("email", "must contain @"), NotFoundError("User", 42), AppError("generic app failure", code=500),]
for err in errors: try: raise err except ValidationError as e: print(f"[{e.code}] Validation on {e.field!r}: {e}") except NotFoundError as e: print(f"[{e.code}] {e.resource} not found (id={e.resource_id})") except AppError as e: print(f"[{e.code}] AppError: {e}")
# --- isinstance hierarchy ---print()e = ValidationError("name", "too short")print(f"ValidationError? {isinstance(e, ValidationError)}")print(f"AppError? {isinstance(e, AppError)}")print(f"Exception? {isinstance(e, Exception)}")print(f"BaseException? {isinstance(e, BaseException)}")Loading Python runtime (first run only)…