Best Practices
Decorator Design Principles
1. Single Responsibility Principle
## Good: Focused decorator
def validate_inputs(cls):
def check_method(method):
def wrapper(*args, **kwargs):
## Single purpose: input validation
if not all(isinstance(arg, int) for arg in args):
raise TypeError("Integer inputs required")
return method(*args, **kwargs)
return wrapper
for name, method in cls.__dict__.items():
if callable(method):
setattr(cls, name, check_method(method))
return cls
2. Decorator Composition
graph TD
A[Base Decorator] --> B[Additional Decorator]
B --> C[Final Enhanced Class]
def logger_decorator(cls):
def log_method(method):
def wrapper(*args, **kwargs):
print(f"Calling {method.__name__}")
return method(*args, **kwargs)
return wrapper
for name, method in cls.__dict__.items():
if callable(method):
setattr(cls, name, log_method(method))
return cls
def performance_decorator(cls):
def time_method(method):
def wrapper(*args, **kwargs):
import time
start = time.time()
result = method(*args, **kwargs)
print(f"Method took {time.time() - start} seconds")
return result
return wrapper
for name, method in cls.__dict__.items():
if callable(method):
setattr(cls, name, time_method(method))
return cls
@logger_decorator
@performance_decorator
class ExampleClass:
def complex_method(self, n):
return sum(range(n))
Practice |
Description |
Impact |
Minimize Overhead |
Avoid complex logic in decorators |
Performance |
Lazy Evaluation |
Defer expensive computations |
Memory Efficiency |
Caching |
Use memoization for repeated calls |
Speed Optimization |
Error Handling Strategies
def robust_decorator(cls):
def safe_method_wrapper(method):
def wrapper(*args, **kwargs):
try:
return method(*args, **kwargs)
except Exception as e:
## Centralized error handling
print(f"Error in {method.__name__}: {e}")
## Optional: logging, fallback, or re-raise
raise
return wrapper
for name, method in cls.__dict__.items():
if callable(method):
setattr(cls, name, safe_method_wrapper(method))
return cls
Decorator Configuration
def configurable_decorator(config=None):
def decorator(cls):
## Dynamic configuration
cls.config = config or {}
return cls
return decorator
@configurable_decorator({"max_retries": 3})
class NetworkClient:
def connect(self):
## Use configuration dynamically
retries = self.config.get('max_retries', 1)
## Connection logic
Advanced Decorator Techniques
import functools
def metadata_preserving_decorator(decorator):
@functools.wraps(decorator)
def wrapped_decorator(cls):
decorated_cls = decorator(cls)
decorated_cls.__name__ = cls.__name__
decorated_cls.__doc__ = cls.__doc__
return decorated_cls
return wrapped_decorator
At LabEx, we recommend using lightweight decorators that:
- Minimize runtime overhead
- Provide clear, focused functionality
- Support easy debugging and maintenance
Common Pitfalls to Avoid
- Overcomplicating decorator logic
- Ignoring performance implications
- Neglecting error handling
- Creating tightly coupled decorators
- Failing to preserve class metadata
Decorator Debugging Tips
def debug_decorator(cls):
print(f"Decorating class: {cls.__name__}")
for name, method in cls.__dict__.items():
if callable(method):
print(f" Method: {name}")
return cls
Scalability Considerations
graph TD
A[Simple Decorator] --> B[Modular Design]
B --> C[Composable Decorators]
C --> D[Scalable Architecture]
By following these best practices, developers can create robust, efficient, and maintainable class decorators that enhance code quality and readability.