functools.wraps

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

PYTHON
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()
Output
wrapper Wrapper function Before function call Hello! After function call

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

PYTHON
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()
Output
say_hello This function says hello Before function call Hello! After function call

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).

Attributes Copied by functools.wraps
AttributeDescription
__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

PYTHON
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__)
Output
greet Say hi None

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

PYTHON
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!")
Output
say Print a message Hello! Hello! 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.wraps is a decorator for decorators to preserve original function metadata.
  • It copies __name__, __doc__, and several other attributes.
  • Using @wraps makes decorated functions introspectable and easier to debug.
  • You should always use @wraps when writing decorators.
Architecture of functools.wraps
Architecture of functools.wraps

💡 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.wraps won’t apply directly, but you can manually set __name__ and __doc__.
  • For async functions, wraps works the same way.
  • Use functools.wraps even in simple one-line decorators to keep your code clean and professional.