Practical Examples
Real-World Decorator Chaining Scenarios
Decorator chaining is not just a theoretical concept, but a powerful technique with numerous practical applications in software development.
1. Authentication and Logging Decorator
def require_login(func):
def wrapper(*args, **kwargs):
if not is_user_authenticated():
raise PermissionError("Login required")
return func(*args, **kwargs)
return wrapper
def log_performance(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} seconds")
return result
return wrapper
@require_login
@log_performance
def sensitive_operation():
## Complex database or API operation
pass
def validate_input(validator):
def decorator(func):
def wrapper(*args, **kwargs):
if not validator(*args, **kwargs):
raise ValueError("Invalid input")
return func(*args, **kwargs)
return wrapper
return decorator
def convert_to_type(target_type):
def decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return target_type(result)
return wrapper
return decorator
@convert_to_type(int)
@validate_input(lambda x: x > 0)
def process_number(value):
return value * 2
Decorator Chaining Workflow
graph TD
A[Input Data] --> B[Validation Decorator]
B --> C[Type Conversion Decorator]
C --> D[Core Function]
D --> E[Final Result]
3. Caching and Retry Mechanism
def retry(max_attempts=3):
def decorator(func):
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts == max_attempts:
raise e
return wrapper
return decorator
def cache_result(func):
cache = {}
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@retry(max_attempts=3)
@cache_result
def fetch_data(url):
## Simulated network request
pass
Decorator Chaining Best Practices
Practice |
Description |
Recommendation |
Order Matters |
Apply decorators from specific to general |
Bottom-up approach |
Performance |
Minimize decorator complexity |
Avoid heavy computations |
Readability |
Keep decorators focused |
Single responsibility principle |
4. Timing and Profiling in LabEx Environment
import time
import functools
def timeit(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
def profile_memory(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
import tracemalloc
tracemalloc.start()
result = func(*args, **kwargs)
current, peak = tracemalloc.get_traced_memory()
print(f"{func.__name__} Memory: Current {current}, Peak {peak}")
tracemalloc.stop()
return result
return wrapper
@timeit
@profile_memory
def complex_computation():
## Computational task
pass
Key Takeaways
- Decorator chaining enables complex behavior modification
- Useful for cross-cutting concerns like logging, authentication
- Supports modular and reusable code design
- Applicable in various domains: web development, data processing
- Powerful technique in Python programming environments like LabEx