Practical Use Cases
Introduction to Decorator Applications
Decorators are versatile tools in Python with numerous practical applications across different domains of software development.
Common Practical Use Cases
1. Logging and Monitoring
def log_function_call(func):
def wrapper(*args, **kwargs):
print(f"Calling function: {func.__name__}")
result = func(*args, **kwargs)
print(f"Function {func.__name__} completed")
return result
return wrapper
@log_function_call
def calculate_total(items):
return sum(items)
calculate_total([1, 2, 3, 4, 5])
import time
def timing_decorator(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@timing_decorator
def slow_function():
time.sleep(2)
return "Completed"
slow_function()
Authentication and Authorization
def require_auth(func):
def wrapper(*args, **kwargs):
user = kwargs.get('user')
if not user or not user.is_authenticated:
raise PermissionError("Authentication required")
return func(*args, **kwargs)
return wrapper
class User:
def __init__(self, authenticated=False):
self.is_authenticated = authenticated
@require_auth
def access_sensitive_data(user):
return "Sensitive Information"
## Usage examples
authenticated_user = User(authenticated=True)
unauthenticated_user = User()
try:
access_sensitive_data(user=authenticated_user)
access_sensitive_data(user=unauthenticated_user)
except PermissionError as e:
print(e)
Caching Mechanism
def memoize(func):
cache = {}
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoize
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(30)) ## Significantly faster on repeated calls
Decorator Use Case Comparison
Use Case |
Purpose |
Key Benefit |
Logging |
Track function calls |
Debugging |
Authentication |
Control access |
Security |
Caching |
Store function results |
Performance |
Timing |
Measure execution time |
Optimization |
def validate_inputs(func):
def wrapper(*args, **kwargs):
for arg in args:
if not isinstance(arg, (int, float)):
raise ValueError("Only numeric inputs allowed")
return func(*args, **kwargs)
return wrapper
@validate_inputs
def divide_numbers(a, b):
return a / b
try:
print(divide_numbers(10, 2))
print(divide_numbers(10, "2")) ## Raises ValueError
except ValueError as e:
print(e)
Rate Limiting with LabEx
def rate_limit(max_calls=3, time_frame=60):
calls = []
def decorator(func):
def wrapper(*args, **kwargs):
import time
current_time = time.time()
calls[:] = [call for call in calls if current_time - call < time_frame]
if len(calls) >= max_calls:
raise Exception("Rate limit exceeded")
calls.append(current_time)
return func(*args, **kwargs)
return wrapper
return decorator
@rate_limit(max_calls=2, time_frame=10)
def api_request():
print("API request processed")
## Demonstrates rate limiting mechanism
Best Practices
- Keep decorators focused and single-purpose
- Use
functools.wraps
to preserve function metadata
- Consider performance implications
- Handle potential exceptions gracefully
Decorators provide a powerful way to extend and modify function behavior without changing their core implementation, making Python code more modular and maintainable.