Decorators
Wrap functions to add logging, caching, auth, and cross-cutting behavior
Overview
Decorators are functions that take a callable and return a new callable, usually extending behavior without modifying the original function body. The @decorator syntax is syntactic sugar for func = decorator(func).
Syntax / Usage
import functools
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_calls
def add(a, b):
return a + b
# Decorator with arguments
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet():
print("Hi")
Built-in decorators include @property, @staticmethod, @classmethod, and @functools.lru_cache.
Examples
Simple timing decorator:
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@timer
def fetch_data():
time.sleep(0.1)
return [1, 2, 3]
Require authentication (conceptual):
def require_auth(handler):
@functools.wraps(handler)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
raise PermissionError("Login required")
return handler(request, *args, **kwargs)
return wrapper
Common Mistakes
- Forgetting
@functools.wraps, losing__name__and docstrings - Decorating functions that need specific signatures without
*args, **kwargs - Stacking decorators in the wrong order—inner decorators run first
- Using decorators for heavy logic that belongs in the function itself
See Also
python-functions python-classes python-context-managers