Advanced Error Handling
Custom exceptions, exception chaining, context managers, and error patterns.
Overview
Master advanced error handling techniques.
Custom Exceptions
# Base exception
class AppError(Exception):
"""Base application exception"""
def __init__(self, message, code=None):
super().__init__(message)
self.code = code
self.message = message
# Specific exceptions
class ValidationError(AppError):
def __init__(self, field, message):
super().__init__(f"Validation error: {field} - {message}")
self.field = field
class NotFoundError(AppError):
def __init__(self, resource, identifier):
super().__init__(f"{resource} not found: {identifier}")
self.resource = resource
self.identifier = identifier
# Usage
def validate_age(age):
if not isinstance(age, int):
raise ValidationError("age", "Must be integer")
if age < 0 or age > 150:
raise ValidationError("age", "Must be between 0 and 150")
return True
try:
validate_age(200)
except ValidationError as e:
print(f"{e.field}: {e.message}")
Exception Chaining
# Implicit chaining
def process_file(filename):
try:
with open(filename) as f:
data = json.load(f)
except FileNotFoundError as e:
raise AppError(f"Cannot process {filename}") from e
# Explicit chaining
def fetch_data(url):
try:
response = requests.get(url)
return response.json()
except requests.RequestException as e:
raise AppError("Failed to fetch data") from e
Context Managers
# Class-based
class DatabaseConnection:
def __init__(self, host, port):
self.host = host
self.port = port
self.connection = None
def __enter__(self):
print(f"Connecting to {self.host}:{self.port}")
self.connection = "Connected"
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Closing connection")
self.connection = None
return False
# Usage
with DatabaseConnection("localhost", 5432) as db:
print(db.connection)
# Function-based context manager
from contextlib import contextmanager
@contextmanager
def managed_resource():
resource = "acquired"
try:
yield resource
except Exception as e:
print(f"Error: {e}")
finally:
print("Releasing resource")
with managed_resource() as r:
print(f"Using {r}")
Error Patterns
# Retry pattern
import time
def retry(max_attempts=3, delay=1):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, delay=1)
def unreliable_function():
import random
if random.random() < 0.5:
raise ValueError("Random failure")
return "Success"
# Fallback pattern
def with_fallback(primary_func, fallback_func):
def wrapper(*args, **kwargs):
try:
return primary_func(*args, **kwargs)
except Exception:
return fallback_func(*args, **kwargs)
return wrapper
Practice
Create a custom exception hierarchy for an e-commerce application.