Practical Decorator Patterns
Common Decorator Use Cases
1. Timing Decorator
import time
from functools import wraps
def timer_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} executed in {end_time - start_time:.4f} seconds")
return result
return wrapper
@timer_decorator
def slow_function():
time.sleep(2)
print("Function completed")
2. Logging Decorator
import logging
from functools import wraps
def log_decorator(level=logging.INFO):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
logging.basicConfig(level=logging.INFO)
logging.log(level, f"Calling {func.__name__}")
try:
result = func(*args, **kwargs)
logging.log(level, f"{func.__name__} completed successfully")
return result
except Exception as e:
logging.error(f"Error in {func.__name__}: {e}")
raise
return wrapper
return decorator
@log_decorator()
def divide(a, b):
return a / b
Decorator Pattern Workflow
graph TD
A[Original Function] --> B[Decorator Applied]
B --> C{Decorator Type}
C --> |Timing| D[Performance Measurement]
C --> |Logging| E[Function Call Logging]
C --> |Caching| F[Result Memoization]
C --> |Authentication| G[Access Control]
Advanced Decorator Patterns
3. Caching Decorator
from functools import wraps, lru_cache
def custom_cache(func):
cache = {}
@wraps(func)
def wrapper(*args, **kwargs):
key = str(args) + str(kwargs)
if key not in cache:
cache[key] = func(*args, **kwargs)
return cache[key]
return wrapper
@custom_cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
Decorator Pattern Comparison
Pattern |
Use Case |
Performance Impact |
Complexity |
Timing |
Performance Measurement |
Low |
Low |
Logging |
Debugging & Monitoring |
Very Low |
Low |
Caching |
Memoization |
Moderate |
Medium |
Authentication |
Access Control |
Low |
High |
from functools import wraps
def validate_inputs(func):
@wraps(func)
def wrapper(*args, **kwargs):
for arg in args:
if not isinstance(arg, (int, float)):
raise TypeError(f"Invalid input type: {type(arg)}")
return func(*args, **kwargs)
return wrapper
@validate_inputs
def add_numbers(a, b):
return a + b
Decorator Composition
@log_decorator()
@timer_decorator
@validate_inputs
def complex_calculation(x, y):
return x ** y
Best Practices
- Use
@functools.wraps
to preserve metadata
- Keep decorators focused and single-purpose
- Consider performance implications
- Handle exceptions gracefully
- Use composition for complex scenarios
LabEx Recommendation
When developing decorators in LabEx projects:
- Prioritize code readability
- Use decorators to separate cross-cutting concerns
- Test decorators thoroughly
- Document decorator behavior clearly