In Python, decorators are a powerful and elegant way to modify or extend the behavior of functions or methods. However, when you create a decorator, it often replaces the __name__ and __doc__ attributes of the original function with those of the wrapper function. This can lead to confusion and difficulties in debugging or introspection.
This is where functools.wraps comes in — a simple yet essential utility that helps preserve the metadata of the original function when applying a decorator.
Why Does Metadata Matter?
Every Python function comes with metadata attributes like:
__name__: The function’s name__doc__: The docstring describing what the function does__module__: The module where the function is defined- Other attributes like
__annotations__and__dict__
This metadata is helpful for:
- Documentation tools
- Debugging and logging
- Introspection during runtime
- Preserving the identity of functions for testing frameworks
When you write a decorator, the wrapper function typically replaces the original function object. This means the wrapper's metadata overwrites the original's, which can mislead users.
📌 Deep Dive: Basic Decorator Without functools.wraps
def my_decorator(func):
def wrapper():
"""Wrapper function"""
print("Before function call")
func()
print("After function call")
return wrapper
@my_decorator
def say_hello():
"""This function says hello"""
print("Hello!")
print(say_hello.__name__)
print(say_hello.__doc__)
say_hello()
Notice: The say_hello function’s name and docstring have been replaced by those of the wrapper function. This is often undesired.
Introducing functools.wraps
The functools.wraps decorator is designed specifically to fix this problem. It copies the metadata from the original function to the wrapper function, preserving its identity.
Under the hood, wraps is a decorator factory that applies update_wrapper, a function that copies attributes like __name__, __doc__, __module__, and others.
How to Use functools.wraps
First, you need to import it from the functools module:
from functools import wraps
Then, apply @wraps(func) to the inner wrapper function inside your decorator, where func is the original function being decorated.
📌 Deep Dive: Using functools.wraps Properly
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper():
"""Wrapper function"""
print("Before function call")
func()
print("After function call")
return wrapper
@my_decorator
def say_hello():
"""This function says hello"""
print("Hello!")
print(say_hello.__name__)
print(say_hello.__doc__)
say_hello()
By adding @wraps(func), the wrapper function now carries the original function’s metadata. This is a best practice when writing decorators.
What Exactly Does wraps Copy?
By default, functools.wraps copies the following attributes from the original function to the wrapper:
__module____name____qualname____doc____annotations__
It also updates the wrapper’s __dict__ with the original function’s dictionary (where additional custom attributes may be stored).
functools.wraps| Attribute | Description |
|---|---|
__module__ | Module name where the function is defined |
__name__ | Function name |
__qualname__ | Qualified name including containing classes |
__doc__ | Function docstring |
__annotations__ | Type annotations |
Customizing functools.wraps
If you want to control which attributes are copied, wraps accepts two optional keyword arguments:
assigned: a tuple of attribute names to assign (copy) from the original function. Defaults to the five attributes listed above.updated: a tuple of attribute names whose values are updated (usually dictionaries like__dict__) instead of assigned. Defaults to('__dict__',).
Here is an example where you only copy __name__ and __doc__:
📌 Deep Dive: Customizing Attributes Copied by wraps
from functools import wraps
def my_decorator(func):
@wraps(func, assigned=('__name__', '__doc__'), updated=())
def wrapper():
print("Calling function")
return func()
return wrapper
@my_decorator
def greet():
"""Say hi"""
print("Hi!")
print(greet.__name__)
print(greet.__doc__)
print(greet.__module__)
Notice the __module__ attribute is not copied because it was excluded using assigned.
Common Use Case: Decorators with Arguments
Many decorators accept arguments themselves. To preserve the metadata in such cases, functools.wraps is applied on the innermost wrapper.
📌 Deep Dive: Decorator with Arguments Using wraps
from functools import wraps
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say(message):
"""Print a message"""
print(message)
print(say.__name__)
print(say.__doc__)
say("Hello!")
Why Not Skip functools.wraps?
Sometimes beginners omit wraps for simplicity, but this leads to these drawbacks:
- Loss of function identity:
__name__,__doc__, etc. show the wrapper instead of the decorated function. - Debugging becomes harder because stack traces show the wrapper function.
- Testing frameworks and documentation generators rely on accurate metadata.
⚠️ Warning
Always use @wraps in your decorators unless you have a very specific reason not to. It ensures your decorated functions behave intuitively and are easier to maintain.
Under the Hood: What Does functools.wraps Do?
functools.wraps is a convenience function that calls functools.update_wrapper with sensible defaults.
update_wrapper performs the actual copying of attributes from the original function to the wrapper function. You can use update_wrapper directly if you need more control.
Summary
functools.wrapsis a decorator for decorators to preserve original function metadata.- It copies
__name__,__doc__, and several other attributes. - Using
@wrapsmakes decorated functions introspectable and easier to debug. - You should always use
@wrapswhen writing decorators.

💡 Key Insight
Think of functools.wraps as a “name tag” that your wrapper function wears so it can introduce itself as the original function. Without it, your wrapper looks like a stranger, confusing anyone who tries to understand what’s going on.
Additional Tips
- If your decorator returns a callable class instead of a function,
functools.wrapswon’t apply directly, but you can manually set__name__and__doc__. - For async functions,
wrapsworks the same way. - Use
functools.wrapseven in simple one-line decorators to keep your code clean and professional.
Quick Knowledge Check
Test what you just learned
Question 1 of 2
What is the main purpose of using functools.wraps in a decorator?
Question 2 of 2
Which attributes does functools.wraps copy by default from the original function to the wrapper? (Select the best answer)
Loading results...