Decorators can be used not only for simple function wrapping, but also for implementing more complex functionalities such as parameterized decorators, class decorators, decorator chains, etc.
Basic Decorator Review
pythonfrom functools import wraps def simple_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Calling function: {func.__name__}") result = func(*args, **kwargs) print(f"Function returned: {result}") return result return wrapper @simple_decorator def add(a, b): return a + b add(3, 5) # Output: # Calling function: add # Function returned: 8
Parameterized Decorators
Decorators with Parameters
pythonfrom functools import wraps def repeat(times): """Decorator that repeats function execution specified number of times""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): results = [] for _ in range(times): result = func(*args, **kwargs) results.append(result) return results return wrapper return decorator @repeat(3) def greet(name): return f"Hello, {name}!" results = greet("Alice") print(results) # Output: ['Hello, Alice!', 'Hello, Alice!', 'Hello, Alice!']
Decorators with Optional Parameters
pythonfrom functools import wraps def logged(level="INFO"): """Decorator with configurable log level""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"[{level}] Calling function: {func.__name__}") try: result = func(*args, **kwargs) print(f"[{level}] Function returned: {result}") return result except Exception as e: print(f"[{level}] Function exception: {e}") raise return wrapper return decorator # Use default log level @logged() def function1(): return "Success" # Use custom log level @logged(level="DEBUG") def function2(): return "Debug info" function1() function2()
Class Decorators
Basic Class Decorator
pythondef add_class_method(cls): """Decorator that adds class method to class""" @classmethod def class_method(cls): return f"Class method: {cls.__name__}" cls.class_method = class_method return cls @add_class_method class MyClass: pass print(MyClass.class_method()) # Class method: MyClass
Singleton Decorator
pythondef singleton(cls): """Singleton pattern decorator""" instances = {} def get_instance(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return get_instance @singleton class Database: def __init__(self): self.connection = "Connected" db1 = Database() db2 = Database() print(db1 is db2) # True
Registry Decorator
pythonclass PluginRegistry: """Plugin registry""" def __init__(self): self.plugins = {} def register(self, name): def decorator(cls): self.plugins[name] = cls return cls return decorator def get_plugin(self, name): return self.plugins.get(name) registry = PluginRegistry() @registry.register("email") class EmailPlugin: def send(self, message): return f"Sending email: {message}" @registry.register("sms") class SMSPlugin: def send(self, message): return f"Sending SMS: {message}" # Use plugin email_plugin = registry.get_plugin("email") print(email_plugin().send("Hello"))
Decorator Chains
Multiple Decorators Stacked
pythonfrom functools import wraps def decorator1(func): @wraps(func) def wrapper(*args, **kwargs): print("Decorator 1 - Before") result = func(*args, **kwargs) print("Decorator 1 - After") return result return wrapper def decorator2(func): @wraps(func) def wrapper(*args, **kwargs): print("Decorator 2 - Before") result = func(*args, **kwargs) print("Decorator 2 - After") return result return wrapper @decorator1 @decorator2 def my_function(): print("Executing function") my_function() # Output: # Decorator 1 - Before # Decorator 2 - Before # Executing function # Decorator 2 - After # Decorator 1 - After
Importance of Decorator Order
pythonfrom functools import wraps def uppercase(func): @wraps(func) def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result.upper() return wrapper def exclamation(func): @wraps(func) def wrapper(*args, **kwargs): result = func(*args, **kwargs) return f"{result}!" return wrapper # Different decorator order produces different results @exclamation @uppercase def greet1(): return "hello" @uppercase @exclamation def greet2(): return "hello" print(greet1()) # HELLO! print(greet2()) # HELLO!
Stateful Decorators
Counting Decorator
pythonfrom functools import wraps def count_calls(func): """Decorator that counts function calls""" @wraps(func) def wrapper(*args, **kwargs): wrapper.call_count += 1 print(f"Function {func.__name__} called {wrapper.call_count} times") return func(*args, **kwargs) wrapper.call_count = 0 return wrapper @count_calls def calculate(x): return x * 2 calculate(5) calculate(10) calculate(15) # Output: # Function calculate called 1 times # Function calculate called 2 times # Function calculate called 3 times
Caching Decorator
pythonfrom functools import wraps def memoize(func): """Memoization decorator""" cache = {} @wraps(func) def wrapper(*args, **kwargs): # Create cache key key = (args, frozenset(kwargs.items())) if key not in cache: print(f"Calculating result: {args}, {kwargs}") cache[key] = func(*args, **kwargs) else: print(f"Using cache: {args}, {kwargs}") return cache[key] wrapper.cache = cache return wrapper @memoize def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2) print(fibonacci(10)) print(fibonacci(10)) # Second time uses cache
Method Decorators
Instance Method Decorator
pythonfrom functools import wraps def method_decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): print(f"Calling method: {func.__name__} on instance {self}") return func(self, *args, **kwargs) return wrapper class MyClass: @method_decorator def method(self, value): return value * 2 obj = MyClass() print(obj.method(5))
Class Method Decorator
pythonfrom functools import wraps def class_method_decorator(func): @wraps(func) def wrapper(cls, *args, **kwargs): print(f"Calling class method: {func.__name__} on class {cls}") return func(cls, *args, **kwargs) return wrapper class MyClass: @classmethod @class_method_decorator def class_method(cls): return f"Class method: {cls.__name__}" print(MyClass.class_method())
Static Method Decorator
pythonfrom functools import wraps def static_method_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Calling static method: {func.__name__}") return func(*args, **kwargs) return wrapper class MyClass: @staticmethod @static_method_decorator def static_method(value): return value * 2 print(MyClass.static_method(5))
Decorator Factory
Dynamically Creating Decorators
pythonfrom functools import wraps def create_validator(**validators): """Factory function that creates validation decorator""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # Validate parameters for param_name, validator in validators.items(): if param_name in kwargs: value = kwargs[param_name] if not validator(value): raise ValueError(f"Parameter {param_name} validation failed: {value}") return func(*args, **kwargs) return wrapper return decorator # Define validators def is_positive(x): return x > 0 def is_string(x): return isinstance(x, str) # Use factory to create decorator @create_validator(age=is_positive, name=is_string) def process_user(name, age): return f"User: {name}, Age: {age}" print(process_user("Alice", 25)) # process_user("Alice", -5) # ValueError: Parameter age validation failed: -5
Decorators with Async Functions
Async Function Decorator
pythonimport asyncio from functools import wraps def async_decorator(func): """Async function decorator""" @wraps(func) async def wrapper(*args, **kwargs): print(f"Async calling function: {func.__name__}") result = await func(*args, **kwargs) print(f"Async function returned: {result}") return result return wrapper @async_decorator async def async_function(): await asyncio.sleep(1) return "Async completed" async def main(): result = await async_function() print(result) asyncio.run(main())
Async Context Manager Decorator
pythonimport asyncio from functools import wraps def async_context_manager(func): """Async context manager decorator""" @wraps(func) async def wrapper(*args, **kwargs): print("Entering async context") try: result = await func(*args, **kwargs) return result finally: print("Exiting async context") return wrapper @async_context_manager async def async_operation(): print("Executing async operation") await asyncio.sleep(0.5) return "Operation completed" asyncio.run(async_operation())
Practical Application Scenarios
1. Permission Validation Decorator
pythonfrom functools import wraps def require_permission(permission): """Permission validation decorator""" def decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): if not hasattr(self, 'permissions'): raise PermissionError("No permission information") if permission not in self.permissions: raise PermissionError(f"Requires {permission} permission") return func(self, *args, **kwargs) return wrapper return decorator class User: def __init__(self, name, permissions): self.name = name self.permissions = permissions @require_permission('admin') def delete_user(self, user_id): return f"Delete user {user_id}" @require_permission('write') def edit_post(self, post_id): return f"Edit post {post_id}" admin = User("Admin", ['admin', 'write']) user = User("User", ['read']) print(admin.delete_user(1)) # Delete user 1 # user.delete_user(1) # PermissionError: Requires admin permission
2. Performance Monitoring Decorator
pythonimport time from functools import wraps def performance_monitor(func): """Performance monitoring decorator""" @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() execution_time = end_time - start_time print(f"Function {func.__name__} execution time: {execution_time:.4f} seconds") return result return wrapper @performance_monitor def calculate_fibonacci(n): if n < 2: return n return calculate_fibonacci(n-1) + calculate_fibonacci(n-2) calculate_fibonacci(30)
3. Retry Decorator
pythonimport time from functools import wraps def retry(max_attempts=3, delay=1): """Retry decorator""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): last_exception = None for attempt in range(max_attempts): try: return func(*args, **kwargs) except Exception as e: last_exception = e if attempt < max_attempts - 1: print(f"Attempt {attempt + 1} failed, retrying in {delay} seconds...") time.sleep(delay) raise last_exception return wrapper return decorator @retry(max_attempts=3, delay=2) def unstable_function(): import random if random.random() < 0.7: raise ValueError("Random failure") return "Success" result = unstable_function() print(result)
4. Logging Decorator
pythonimport logging from functools import wraps def logged(func): """Logging decorator""" @wraps(func) def wrapper(*args, **kwargs): logging.info(f"Calling function: {func.__name__} args: {args}, kwargs: {kwargs}") try: result = func(*args, **kwargs) logging.info(f"Function {func.__name__} returned: {result}") return result except Exception as e: logging.error(f"Function {func.__name__} exception: {e}") raise return wrapper # Configure logging logging.basicConfig(level=logging.INFO) @logged def divide(a, b): return a / b divide(10, 2) # divide(10, 0) # Will log error
5. Caching Decorator
pythonfrom functools import lru_cache import time @lru_cache(maxsize=128) def expensive_computation(n): """Expensive computation""" print(f"Calculating {n}...") time.sleep(1) return n ** 2 # First call print(expensive_computation(5)) # Calculating 5... 25 # Second call (uses cache) print(expensive_computation(5)) # 25 (Direct return, no calculation)
Best Practices
1. Use functools.wraps
pythonfrom functools import wraps # Good practice - Preserve function metadata def good_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper # Bad practice - Don't preserve function metadata def bad_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper
2. Handle Decorator Parameters
pythonfrom functools import wraps def decorator_with_args(*decorator_args, **decorator_kwargs): """Decorator that accepts parameters""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # Use decorator parameters print(f"Decorator parameters: {decorator_args}, {decorator_kwargs}") return func(*args, **kwargs) return wrapper return decorator @decorator_with_args("arg1", "arg2", kwarg1="value1") def my_function(): return "Hello" my_function()
3. Decorators Should Be Side-Effect Free
pythonfrom functools import wraps # Good practice - No side effects def pure_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper # Bad practice - Has side effects counter = 0 def impure_decorator(func): @wraps(func) def wrapper(*args, **kwargs): global counter counter += 1 return func(*args, **kwargs) return wrapper
4. Provide Clear Documentation
pythonfrom functools import wraps def timing_decorator(func): """Timing decorator This decorator measures the execution time of a function. Execution time is printed to the console. Example: @timing_decorator def my_function(): time.sleep(1) return "Done" """ @wraps(func) def wrapper(*args, **kwargs): import time start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"{func.__name__} execution time: {end_time - start_time:.4f} seconds") return result return wrapper
Summary
Core concepts of Python decorator advanced applications:
- Parameterized Decorators: Decorators that accept parameters, providing more flexibility
- Class Decorators: Used to decorate classes, adding class-level functionality
- Decorator Chains: Multiple decorators stacked together, note execution order
- Stateful Decorators: Decorators that maintain internal state
- Method Decorators: Used to decorate instance methods, class methods, static methods
- Decorator Factory: Functions that dynamically create decorators
- Async Decorators: Used to decorate async functions
Practical applications of decorators:
- Permission validation
- Performance monitoring
- Error handling and retry
- Logging
- Caching and memoization
- Parameter validation
Best practices for decorators:
- Use
functools.wrapsto preserve function metadata - Properly handle decorator parameters
- Keep decorators side-effect free
- Provide clear documentation and examples
- Consider decorator performance impact
Mastering advanced decorator applications enables writing more powerful and flexible Python code.