Custom Exceptions
Define application-specific error types by subclassing Exception (or a more specific built-in).
Basic Custom Exception
class InsufficientFundsError(Exception):
pass
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(f"Cannot withdraw {amount}, balance is {balance}")
return balance - amount
try:
withdraw(100, 500)
except InsufficientFundsError as e:
print(e) # 'Cannot withdraw 500, balance is 100'Adding Custom Attributes and Behavior
class InsufficientFundsError(Exception):
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
self.shortfall = amount - balance
message = f"Need {self.shortfall} more to withdraw {amount}"
super().__init__(message)
try:
raise InsufficientFundsError(balance=100, amount=500)
except InsufficientFundsError as e:
print(e.shortfall) # 400, structured data, not just a message stringBuilding an Exception Hierarchy
Organizing related exceptions under a common base lets calling code catch broadly OR narrowly, as needed.
class AppError(Exception):
"""Base exception for all application-specific errors."""
pass
class ValidationError(AppError):
pass
class DatabaseError(AppError):
pass
class RecordNotFoundError(DatabaseError):
pass
try:
raise RecordNotFoundError("User 42 not found")
except DatabaseError as e: # catches RecordNotFoundError too, since it's a subclass
print(f"DB issue: {e}")
except AppError as e:
print(f"App issue: {e}")Design pattern: one base exception per package/app
Define a root exception (e.g.
AppError) for your project. Calling code that wants to catch “anything my app might throw, but not unrelated library/system errors” can catch just that base class.
When to Create a Custom Exception vs Reuse a Built-in
Guideline
Create a custom exception when the error represents a distinct, meaningful condition in YOUR domain logic that calling code might want to handle differently (e.g.
InsufficientFundsErrorvs a genericValueError). Reuse a built-in (ValueError,TypeError) for straightforward, generic argument problems that don’t need special handling.
Custom Exceptions with Multiple Constructor Arguments
class APIError(Exception):
def __init__(self, status_code, message):
self.status_code = status_code
super().__init__(f"[{status_code}] {message}")
try:
raise APIError(404, "User not found")
except APIError as e:
print(e.status_code) # 404
print(str(e)) # '[404] User not found'