A decorator wraps a function or class to extend behavior—syntax @decorator above def. Functions are first-class objects in Python, enabling higher-order patterns like middleware in the Django track.
Function decorator
def log_calls(fn):
def wrapper(*args, **kwargs):
print(f"calling {fn.__name__}")
return fn(*args, **kwargs)
return wrapper
@log_calls
def add(a, b):
return a + b
functools.wraps
from functools import wraps
def deco(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
return fn(*args, **kwargs)
return wrapper
@wraps preserves metadata (__name__, docstring) on the wrapper—important for debugging and introspection.
Important interview questions and answers
- Q: What does @decorator do?
A: Rebinds the name todecorator(original_function)—syntactic sugar for wrapper assignment. - Q: Decorator with arguments?
A: Requires an outer factory returning the actual decorator—three nested functions.
Self-check
- Why use functools.wraps?
- Are decorators compile-time or runtime?
Tip: Use @wraps(fn) in decorators—preserves __name__ for pytest and debugging.
Interview prep
- What @deco does?
Replaces function with deco(original)—applied at function definition time.
- functools.wraps?
Copies metadata to wrapper so introspection and pytest see original __name__.