Decorators
Decorators are Python’s special syntax to run additional code before and after each function call it wraps.
- Decorators have access to the function’s input arguments, return values and raised exceptions
- Useful for enforcing semantics, debugging, registering functions, etc.
Example of a Decorator for Debugging
This example is from Effective Python book and is for debugging the stack of nested function calls from a recursive function.
def trace(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(f"{func.__name__}({args!r}, {kwargs!r}) "
f"-> {result!r}")
return result
return wrapper
@trace
def fibonacci(n):
"""Return the n-th Fibonacci number"""
if n in (0, 1):
return n
return (fibonacci(n - 2) + fibonacci(n - 1))
@trace
is equivalent to wrapping fibonacci
function as below:
fibonacci = trace(fibonacci)
The decorated function runs the wrapper
code before and after fibonacci
runs.
Therefore, it prints arguments and return values at each level in the recursive stack.
Side Effects of Decorators
The value returned by the decorator points at trace.<locals>.wrapper
instead of the fibonacci
function.
This can be problematic when we use debugger to introspect the code.
functools.wraps
The solution toe the above side effect is to use functools
built-in module.
functools
has a decorator that helps write decorators by copying all of the important metadata about the inner function to the outer function.
from functools import wraps
def trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return wrapper
@trace
def fibonacci(n):
...
Now, when we take a look at help(fibonacci)
, it returns description of fibonacci
function instead of wrapper
.
Reference
- Effective Python