def Decorator / @ Syntax
| Since: | Python 2(2000) |
|---|
A decorator is a mechanism that takes a function or class and returns a new function (or class) with added functionality. Writing @decorator_name immediately before a function definition applies the decorator at the time the function is defined. It is common practice to use functools.wraps() when writing your own decorators to preserve the original function's metadata.
Syntax
from functools import wraps
# Define a decorator
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Pre-processing
result = func(*args, **kwargs)
# Post-processing
return result
return wrapper
# Apply the decorator
@my_decorator
def my_function():
pass
# Decorator that accepts arguments
def decorator_with_args(arg1, arg2):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return decorator
Syntax / Functions
| Syntax / Function | Description |
|---|---|
| @decorator_name | Written before a function definition to apply the decorator. |
| @wraps(func) | Copies the metadata of the original func (such as __name__) to the wrapper. |
| *args, **kwargs | A pattern used inside a decorator to accept all arguments of the wrapped function. |
| @deco1 @deco2 | Stacking decorators. They are applied from bottom to top (deco2 first, then deco1). |
Sample Code
The following examples show advanced usage.
decorator.py
from functools import wraps
import time
# Decorator that measures execution time
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"[{func.__name__}] elapsed: {elapsed:.4f}s")
return result
return wrapper
@timer
def heavy_process(n):
"""Simulates a heavy computation"""
return sum(range(n))
print(heavy_process(1000000)) # [heavy_process] elapsed: 0.XXXXs
# Decorator with arguments (retry on failure)
def retry(times=3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i in range(times):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Attempt {i+1}/{times} failed: {e}")
raise RuntimeError(f"{func.__name__} failed after {times} attempts")
return wrapper
return decorator
@retry(times=3)
def unstable_api():
import random
if random.random() < 0.7:
raise ConnectionError("Connection error")
return "Success"
# Stacking decorators (applying multiple decorators)
def bold(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f"**{func(*args, **kwargs)}**"
return wrapper
def upper(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
@bold # Applied second (outer)
@upper # Applied first (inner)
def greet(name):
return f"hello, {name}"
print(greet('world')) # **HELLO, WORLD** (upper → bold order)
# Class-based decorator
class Memoize:
def __init__(self, func):
self.func = func
self.cache = {}
wraps(func)(self) # Copy metadata
def __call__(self, *args):
if args not in self.cache:
self.cache[args] = self.func(*args)
return self.cache[args]
@Memoize
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(30)) # 832040 (fast thanks to caching)
python3 decorator.py [heavy_process] elapsed: 0.0059s 499999500000 **HELLO, WORLD** 832040
Practical Pattern: Common Web Application Decorators
web_decorators.py
from functools import wraps
# Logging decorator (records arguments and return values)
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[LOG] {func.__name__} called with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"[LOG] {func.__name__} returned {result}")
return result
return wrapper
# Authentication check decorator
def require_login(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if not user.get('is_authenticated'):
raise PermissionError("Login required")
return func(user, *args, **kwargs)
return wrapper
# Cache decorator (simple memoization)
def cache(func):
store = {}
@wraps(func)
def wrapper(*args):
if args not in store:
store[args] = func(*args)
return store[args]
return wrapper
@log_calls
@require_login
def get_profile(user):
return {"name": "Okabe Rintaro", "lab": "Future Gadget Lab"}
@cache
def expensive_calc(n):
return sum(range(n))
user = {"is_authenticated": True}
get_profile(user)
print(expensive_calc(1000000)) # computed on first call
print(expensive_calc(1000000)) # returned from cache on subsequent calls
python3 web_decorators.py
[LOG] get_profile called with args=({'is_authenticated': True},), kwargs={}
[LOG] get_profile returned {'name': 'Okabe Rintaro', 'lab': 'Future Gadget Lab'}
499999500000
499999500000
Common mistake 1: omitting @wraps(func)
Omitting @wraps(func) causes the function name and docstring to be lost.
decorator_wraps_ng.py
from functools import wraps
# NG: Omitting @wraps(func) causes the function name and docstring to be lost.
def bad_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper # no wraps
@bad_decorator
def greet(name):
"""Returns a greeting."""
return f"Hello, {name}"
print(greet.__name__) # prints 'wrapper'
print(greet.__doc__) # prints None
python3 decorator_wraps_ng.py wrapper None
decorator_wraps_ok.py
from functools import wraps
# OK: Always use @wraps(func).
def good_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@good_decorator
def greet(name):
"""Returns a greeting."""
return f"Hello, {name}"
print(greet.__name__) # prints 'greet'
print(greet.__doc__) # prints 'Returns a greeting.'
python3 decorator_wraps_ok.py greet Returns a greeting.
Common mistake 2: missing parentheses on parameterized decorator
Forgetting the parentheses on a decorator that takes arguments causes a TypeError. (retry is the decorator factory defined in the sample code section above.)
decorator_args_ng.py
# NG: Forgetting the parentheses on a decorator that takes arguments causes a TypeError.
@retry # NG: retry is a decorator factory — parentheses are required.
def my_func():
pass
python3 decorator_args_ng.py Traceback (most recent call last): ... TypeError: wrapper() takes 0 positional arguments but 1 was given
decorator_args_ok.py
from functools import wraps
def retry(times=3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i in range(times):
try:
return func(*args, **kwargs)
except Exception as e:
if i == times - 1:
raise
print(f"Retry {i + 1}/{times}: {e}")
return wrapper
return decorator
@retry(times=3)
def stable_func():
print("Executed successfully")
stable_func()
python3 decorator_args_ok.py Executed successfully
Notes
A decorator is syntactic sugar in Python. Writing @my_decorator is equivalent to func = my_decorator(func). When decorators are stacked, they are applied from the bottom up (innermost to outermost).
A decorator that accepts arguments requires three levels of nested functions, but the structure is straightforward once you understand it: the outer function receives the arguments, the middle function receives the target function, and the innermost wrapper performs the actual wrapping logic.
Decorators are ideal for implementing cross-cutting concerns such as logging, authentication checks, caching, and validation. They help avoid code duplication and support the DRY (Don't Repeat Yourself) principle.
If you find any errors or copyright issues, please contact us.