90 – Preserving decorated function metadata

90 – Preserving decorated function metadata#

The standard way of writing a decorator involves creating a wrapper function for the decorated function, like so:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        ...  # Decorator code goes here.
    return wrapper

Following this strategy has the drawback of “erasing” important function metadata.

Suppose you apply the decorator my_decorator to a function:

@my_decorator
def add(a, b):
    """Performs addition!"""
    return a + b

In doing so, the metadata of the function add was messed up. Printing the function now shows a funky result:

print(add)  # <function my_decorator.<locals>.wrapper at 0x102cb4510>

Similarly, using the built-in help(add) will reveal a cryptic help message:

Help on function wrapper in module __main__:

wrapper(*args, **kwargs)
 Help on my_decorator.<locals>.wrapper line 1/3 (END) (press h for help or q to quit)

To fix this behaviour – which is technically correct but definitely unhelpful – you can use the decorator functools.wraps inside your decorator:

import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        ... # Decorator code goes here...
    return wrapper

The usage of functools.wraps will ensure the metadata is passed on to the wrapper. If you reapply the decorator my_decorator to a fresh function add you will see the result looks good:

print(add)  # <function add at 0x102db19b0>

And using help(add) reveals:

Help on function add in module __main__:

add(a, b)
    Performs addition!
 Help on add line 1/4 (END) (press h for help or q to quit)

Further reading: