functools.wraps and preserving metadata

RoadmapsPython

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

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

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

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

Next: Decorator Factories with Arguments

Return to Python Roadmap