How to Write a Python Decorator with functools.wraps
Create a decorator that wraps a function while preserving its metadata using functools.wraps.
Python code
21 linesfrom functools import wraps
def logger(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@logger
def greet(name):
"""Return a friendly greeting."""
return f"Hello, {name}!"
if __name__ == "__main__":
print(greet("Alice"))
print(f"Function name: {greet.__name__}")
print(f"Docstring: {greet.__doc__}")
Output
Calling greet
Hello, Alice!
Function name: greet
Docstring: Return a friendly greeting.
How it works
The @wraps(func) decorator copies the original function's __name__, __doc__, and other metadata onto the wrapper function. This is crucial for debugging and for tools like Sphinx or pytest that rely on function signatures and docstrings. Without @wraps, the wrapper would show up as wrapper instead of greet, and its docstring would be None. The *args, **kwargs signature allows the wrapper to accept any arguments, ensuring that the decorated function works with any call signature.
Common mistakes
- Forgetting to use `@wraps(func)` and losing the original function's metadata.
- Not returning `func(*args, **kwargs)` from the wrapper, causing the original function's return value to be lost.
- Assuming the wrapper needs to handle specific argument names, when using `*args, **kwargs` is more flexible.
Variations
- Use `functools.wraps` with a decorator factory to accept arguments, e.g., `@logger(level='info')`.
- Apply `@wraps` inside a class-based decorator to preserve metadata on the `__call__` method.
Real-world use cases
- Logging function calls with timing or parameters in a production service, keeping the original function name in logs.
- Adding caching or memoization to functions while preserving their signatures for type checkers and documentation.
- Creating access-control decorators that raise authorization errors but preserve the original function's introspection.
Sponsored
More from Functions & basics
- Add Type Hints to Function Parameters and Return in Python easy
- Benchmark list append vs comprehension in Python easy
- Build a Context Manager in Python with contextlib.contextmanager easy
- Build a Progress Callback Function for Loops in Python easy
- Cache expensive function with lru_cache in Python easy
- Calculate Time Difference Across Time Zones in Python easy
Keep learning
Related tutorials and quizzes for this topic.