Introduction
One common challenge with decorators is that they can unintentionally strip away important metadata from the functions they decorate. This metadata includes attributes such as the function’s docstring, name, module, and signature. When a function is decorated, the attributes of the wrapper function, rather than the original function, are exposed to introspection tools like help()
or when accessing attributes like __name__
.
Losing this metadata can be problematic, especially in scenarios where the original function’s attributes are relied upon for documentation, debugging, or integration with other tools. For instance, if you’re building a library or framework where users need to inspect or interact with functions programmatically, having accurate metadata is crucial for a smooth developer experience.
Fortunately, there is a solution to this problem in the form of the Python wraps
decorator, which is available in the functools
module. You can use wraps
in any decorator you create to preserve the original metadata.
The Problem the Python wraps Decorator Solves
Consider the scenario where you have a decorated function. When you retrieve the function’s docstring using __doc__, or access its __name__
attribute, you might find that the information displayed pertains to the decorator’s inner function rather than the original function.
Example:
def greeting_decorator(fn): def decorated(*args, **kwargs): ''' This decorator prints a greeting.''' print("Hi.") return fn(*args, **kwargs) return decorated @greeting_decorator def introduce_yourself(): ''' This function prints an introduction.''' print("My name is Bobby, I'm 4 years old, and I like trucks.") print("Function Name:") print(introduce_yourself.__name__) print() print("Documentation:") print(introduce_yourself.__doc__) print() print("Output:") introduce_yourself()
Output:
Function Name: decorated Documentation: This decorator prints a greeting. Output: Hi. My name is Bobby, I'm 4 years old, and I like trucks.
The example above creates a simple decorator (lines 1-6) that prints a greeting prior to the original function’s execution. The function introduce_yourself()
(line 9) prints an introduction sentence from a 4-year-old named Bobby (line 11), and it’s decorated by @greeting_decorator
(line 8), which adds a printout of “Hi.” Next, the code prints the decorated function’s name using introduce_yourself.__name__
(line 14), its docstring using introduce_yourself.__doc__
(line 18), and finally, executes the function itself (line 22).
As you can see from the output, the function’s name and docstring are those of decorated()
(line 2), which is the inner function of the decorator. The function name and docstring print out as “decorated” and “This decorator prints a greeting”, respectively, even though the function is introduce_yourself()
.
Using the wraps Decorator to Retain Function Metadata
The wraps
decorator is straightforward to use. You simply apply @wraps
to the inner function that does the decorating within your decorator, giving it the function as an argument. By doing so, you instruct Python to copy the metadata from the original function to the wrapper function.
from functools import wraps def greeting_decorator(fn): @wraps(fn) def decorated(*args, **kwargs): ''' This decorator prints a greeting.''' print("Hi.") return fn(*args, **kwargs) return decorated @greeting_decorator def introduce_yourself(): ''' This function prints an introduction.''' print("My name is Bobby, I'm 4 years old, and I like trucks.") print("Function Name:") print(introduce_yourself.__name__) print() print("Documentation:") print(introduce_yourself.__doc__) print() print("Output:") introduce_yourself()
Output:
Function Name: introduce_yourself Documentation: This function prints an introduction. Output: Hi. My name is Bobby, I'm 4 years old, and I like trucks.
The example above is identical to the previous once, except that it utilizes the wraps
decorator. After importing form functools
(line 1), @wraps(fn)
is applied to the decorated()
function (line 4). This ensures that decorated()
retains the docstring, name, and other metadata of the original function, fn
.
In the printed output, the function name is now indeed “introduce_yourself”, and docstring is “This function prints an introduction.” Both pieces of metadata are taken from the original introduce_yourself()
function.
Here is sample code that summarizes how to use @wraps
within a decorator function:
from functools import wraps def decorator_function(original_function): @wraps(original_function) def modified_function(*args, **kwargs): ... # decorator code return modified_function
These are the key elements:
from functools import wraps
: The import statement that imports thewraps
decorator.decorator_function
: The decorator function that takes the original function as its argument.original_function
: The function to be modified, i.e. decorated.modified_function
: The inner function that modifies the original function (a.k.a. wrapper).@wraps(original_function)
: The call to the@wraps
decorator, which decoratesmodified_function()
and makes it preserve the metadata oforiginal_function
.
Summary & Reference to the Python wraps Decorator
The wraps
decorator is a member of the functools
module, and functions to preserve metadata when using decorators. Without it, decorators strip away information from decorated functions, such as their docstrings, names, and signatures.
By employing @wraps
, decorators can maintain the original function’s metadata. This is particularly critical for documentation, debugging, or integration with other tools.
Applying @wraps
is straightforward. Simply decorate the inner function of your decorator with @wraps
and pass the original function as an argument. This instructs Python to copy the metadata from the original function to the function created by the decorator.
from functools import wraps def decorator_function(original_function): @wraps(original_function) def modified_function(*args, **kwargs): ... # decorator code return modified_function