In Python, functions are “first-class citizens”—they can be passed as arguments, assigned to variables, or even returned as values. Decorators leverage this feature to “dynamically add functionality to functions” without modifying the original function’s code. It’s like wrapping a gift box with beautiful paper, preserving the gift itself while adding a new appearance.
1. Why Decorators Are Needed?¶
Suppose we want to add “print execution logs” functionality to multiple functions. Directly modifying each function would lead to repetitive and hard-to-maintain code. For example:
# Original function 1
def add(a, b):
return a + b
# Original function 2
def subtract(a, b):
return a - b
To add logging, the straightforward approach would be to manually modify each function:
# Log-added add function
def add(a, b):
print(f"Function add started, parameters: a={a}, b={b}")
result = a + b
print(f"Function add finished, result: {result}")
return result
# Log-added subtract function (repetitive code)
def subtract(a, b):
print(f"Function subtract started, parameters: a={a}, b={b}")
result = a - b
print(f"Function subtract finished, result: {result}")
return result
This is clearly inelegant. If there are 100 functions, you’d repeat the logging code 100 times. Decorators solve this “reinventing the wheel” problem.
2. Basic Principle of Decorators¶
A decorator is essentially a function that takes a function as input and returns a new function (the “wrapper” that adds extra functionality around the original function).
1. Simplest Decorator¶
Let’s define a “log decorator” that adds “start/end execution” logs to any function:
def log_decorator(func): # Accepts the original function `func`
def wrapper(*args, **kwargs): # Internal wrapper function
# 1. Add functionality: print start log
print(f"Function {func.__name__} started, parameters: {args}, {kwargs}")
# 2. Call the original function
result = func(*args, **kwargs)
# 3. Add functionality: print end log
print(f"Function {func.__name__} finished, result: {result}")
return result
return wrapper # Return the wrapped function
2. Using the Decorator¶
Use the @ syntax (decorator sugar) to “wrap” the original function:
@log_decorator # Equivalent to: add = log_decorator(add)
def add(a, b):
return a + b
@log_decorator # Equivalent to: subtract = log_decorator(subtract)
def subtract(a, b):
return a - b
3. Testing the Call¶
When you call add(1, 2), the wrapper function executes, automatically printing logs:
add(1, 2)
# Output:
# Function add started, parameters: (1, 2), {}
# Function add finished, result: 3
3. Core Details of Decorators¶
1. *args and **kwargs¶
*argscaptures positional arguments (e.g.,1, 2inadd(1, 2)).**kwargscaptures keyword arguments (e.g.,a=1, b=2inadd(a=1, b=2)).- This ensures the decorator works with any function’s parameters.
2. Preserving Original Function Metadata¶
Without modification, add.__name__ would return wrapper (since add is replaced by wrapper). Use functools.wraps to preserve metadata:
from functools import wraps
def log_decorator(func):
@wraps(func) # Preserves original function metadata
def wrapper(*args, **kwargs):
print(f"Function {func.__name__} started")
result = func(*args, **kwargs)
print(f"Function {func.__name__} finished")
return result
return wrapper
print(add.__name__) # Output: add (not "wrapper")
4. Decorators with Parameters¶
To pass parameters to a decorator (e.g., custom log prefixes), nest two layers of functions:
def log_decorator(prefix="【Log】"): # Outer function: accepts decorator arguments
def decorator(func): # Inner function: accepts original function
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{prefix} Function {func.__name__} started")
result = func(*args, **kwargs)
print(f"{prefix} Function {func.__name__} finished")
return result
return wrapper
return decorator # Return the inner decorator
# Usage with parameters
@log_decorator(prefix="【Debug】")
def multiply(a, b):
return a * b
multiply(3, 4)
# Output:
# 【Debug】 Function multiply started
# 【Debug】 Function multiply finished
5. Use Cases of Decorators¶
Decorators are flexible for scenarios like:
- Logging: Record function calls, parameters, and return values.
- Performance Testing: Measure execution time (e.g., @timer_decorator).
- Permission Verification: Check user permissions before execution (e.g., @check_permission).
- Caching: Cache results to avoid redundant computations (e.g., @cache_decorator).
6. Execution Order of Multiple Decorators¶
When multiple decorators wrap a function, they execute from bottom to top (the decorator closer to the function runs first):
@decorator2
@decorator1
def func():
pass
Execution order: decorator2 → decorator1 → func → decorator1 → decorator2.
Summary¶
Decorators are Python’s “magic tools” that use function nesting and closures to add functionality elegantly. Key points:
1. A decorator is a function that takes a function and returns a new function.
2. The @ syntax simplifies applying decorators.
3. *args/**kwargs handle arbitrary parameters, and functools.wraps preserves metadata.
4. Parameterized decorators require two nested functions.
From simple logging to complex permission checks, decorators make code cleaner and more maintainable. Try adding a log decorator to your own function to experience its convenience!