functools.wraps and preserving metadata
Overview
When you write a decorator, the returned wrapper function replaces the original function. By default, this wrapper hides the original function's metadata, such as its `__name__` and `__doc__`. Using `@functools.wraps(func)` on the wrapper copies the original function's metadata to the wrapper. Think of it like wearing a costume: without `@wraps`, people see the costume and forget who is inside; with `@wraps`, you wear a name tag over the costume so your real identity is preserved. import functools def my_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper When building decorators that accept arguments, you must apply `@wraps` to the innermost wrapper, not the outer factory function. Applying it to the middle `decorator` function will fail to copy the metadata from the actual target function. def repeat(times): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper return decorator This layered architecture and scope trap is covered fully in `decorator-factories`.
Web frameworks and documentation generators rely on function metadata, like `__name__`, to register routes and build documentation. If decorators obscure this, frameworks fail to map URLs correctly.
Where used: FastAPI routes, Flask views, Celery tasks
Why learn this
- Ensuring your decorators don't break web frameworks that inspect function `__name__` attributes.
- Generating accurate documentation for decorated functions using tools like `Sphinx` or `pydoc`.
Code walkthrough
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper():
return func()
return wrapper
@my_decorator
def real_function():
pass
print(real_function.__name__)
Focus: print(real_function.__name__)
Common mistakes
- Missing @wraps on decorators: Forgetting to decorate the inner `wrapper` with `@functools.wraps(func)`. This causes the decorated function's `__name__` to become `'wrapper'`, breaking reflection tools and web framework routing.
- Applying @wraps to the wrong function scope: In a decorator factory, placing `@functools.wraps(func)` on the middle `decorator` function instead of the innermost `wrapper` function. The `wrapper` is what actually replaces the target, so it must be the function decorated by `@functools.wraps`.
Glossary
- metadata
- Information that describes other data, like a function's name or its documentation. Example: `func.__name__`
- reflection
- The ability of a program to examine and modify its own structure and behavior while it is running. Example: `inspect.signature(func)`
Recall questions
- What happens to a function's `__name__` attribute if you decorate it with a custom decorator that doesn't use `functools.wraps`?
- Why do frameworks like `FastAPI` or `Flask` require decorators to use `functools.wraps`?
- Where should you apply `@functools.wraps` when writing a decorator factory that accepts arguments?
Understanding checks
What does this script print?
wrapper
Because `greet` is replaced by `wrapper`, its `__name__` becomes `'wrapper'`. Without `@functools.wraps`, the original identity is lost.
Why would missing `@functools.wraps` on a decorator cause a `FastAPI` application to fail when defining routes?
`FastAPI` uses the function's `__name__` and type hints to register routes and build documentation.
If a decorator hides the original function's metadata, `FastAPI` only sees the `wrapper` function's name (often literally `'wrapper'`) and signature, losing the actual route details.
Practice tasks
Fix a broken decorator
The provided `log_calls` decorator currently hides the original function's name and docstring. Import `functools` and use `@functools.wraps` to fix the decorator so that `calculate_tax.__name__` prints `'calculate_tax'`.
Challenge
Create a well-behaved timing decorator
Write a `time_it` decorator that measures execution time and prints it. It must use `functools.wraps` so the decorated function retains its `__doc__` and `__name__`. Test it on a function named `process_data`.
Continue learning
Previous: Function Decorators
Previous: Closures