functools is a standard Python module for higher-order functions (functions that act on or return other functions). wraps() is a decorator that is applied to the wrapper function of a decorator. It updates the wrapper function to look like wrapped function by copying attributes such as __name__, __doc__ (the docstring), etc.
Syntax: @functools.wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES)
Parameters:
wrapped: The function name that is to be decorated by wrapper function.
assigned : Tuple to specify which attributes of the original function are assigned directly to the matching attributes on the wrapper function. By default set to WRAPPER_ASSIGNMENTS (which assigns to the wrapper function’s __module__, __name__, __qualname__, __annotations__ and __doc__, the documentation string)
updated : Tuple to specify which attributes of the wrapper function are updated with the corresponding attributes from the original function. By default set to WRAPPER_UPDATES (which updates the wrapper function’s __dict__, i.e. the instance dictionary).
Example 1: Without functools.wraps()
Python3
def a_decorator(func): def wrapper( * args, * * kwargs): """A wrapper function""" # Extend some capabilities of func func() return wrapper @a_decorator def first_function(): """This is docstring for first function""" print ( "first function" ) @a_decorator def second_function(a): """This is docstring for second function""" print ( "second function" ) print (first_function.__name__) print (first_function.__doc__) print (second_function.__name__) print (second_function.__doc__) |
wrapper A wrapper function wrapper A wrapper function
Now what will happen if we write help(first_function) and help(second_function)
Python3
print ( "First Function" ) help (first_function) print ( "\nSecond Function" ) help (second_function) |
First Function Help on function wrapper in module __main__: wrapper(*args, **kwargs) A wrapper function Second Function Help on function wrapper in module __main__: wrapper(*args, **kwargs) A wrapper function
While the above code will work logically fine, but consider this if you are writing an API or a library and someone want to know what your function does and it’s name or simply type help(yourFunction), it will always show wrapper function’s name and docstring. This gets more confusing if you have used the same wrapper function for different functions, as it will show the same details for each one of them.
Ideally it should show the name and docstring of wrapped function instead of wrapping function. Manual solution would be to assign __name__, __doc__ attributes in the wrapping function before returning it.
Python3
def a_decorator(func): def wrapper( * args, * * kwargs): """A wrapper function""" # Extend some capabilities of func func() wrapper.__name__ = func.__name__ wrapper.__doc__ = func.__doc__ return wrapper @a_decorator def first_function(): """This is docstring for first function""" print ( "first function" ) @a_decorator def second_function(a): """This is docstring for second function""" print ( "second function" ) print (first_function.__name__) print (first_function.__doc__) print (second_function.__name__) print (second_function.__doc__) |
first_function This is docstring for first function second_function This is docstring for second function
This solves the problem, but what if we again type help(yourFunction),
Python3
print ( "First Function" ) help (first_function) print ( "\nSecond Function" ) help (second_function) |
For first_function: help(first_function)
First Function Help on function first_function in module __main__: first_function(*args, **kwargs) This is docstring for first function Second Function Help on function second_function in module __main__: second_function(*args, **kwargs) This is docstring for second function
As you can see it still has an issue, i.e. the signature of the function, it is showing signature used by wrapper function (here, generic signature) for each of them. Also if you are implementing many decorators, then you have to write these lines for each one of them.
So to save time and increase readability, we could use functools.wraps() as decorator to wrapper function.
Example (with functools.wraps())
Python3
from functools import wraps def a_decorator(func): @wraps (func) def wrapper( * args, * * kwargs): """A wrapper function""" # Extend some capabilities of func func() return wrapper @a_decorator def first_function(): """This is docstring for first function""" print ( "first function" ) @a_decorator def second_function(a): """This is docstring for second function""" print ( "second function" ) print (first_function.__name__) print (first_function.__doc__) print (second_function.__name__) print (second_function.__doc__) |
first_function This is docstring for first function second_function This is docstring for second function
Now, if we type help(first_function) –
Python3
print ( "First Function" ) help (first_function) print ( "\nSecond Function" ) help (second_function) |
First Function Help on function first_function in module __main__: first_function() This is docstring for first function Second Function Help on function second_function in module __main__: second_function(a) This is docstring for second function