The functools module in Python deals with higher order functions, that is, functions operating on(taking as arguments) or returning functions and other such callable objects. The functools module provides a wide array of methods such as cached_property(func), cmp_to_key(func), lru_cache(func), wraps(func), etc. It is worth noting that these methods take functions as arguments. In this article, we will discuss the purpose and the application of the update_wrapper() method provided by the functools module. This method is used to update the metadata of the wrapper function to reflect that of the wrapped function which allows better readability and re-usability of code. The update_wrapper() method takes the following arguments:
Syntax:@functools.update_wrapper(wrapper, wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES)
Parameters:
- wrapper: A wrapper function.
- wrapped: The function being wrapped over or the wrapped function.
- assigned: Attributes of the wrapped function which are assigned to the matching attributes of wrapper function as a tuple(optional argument).
- updated: Attributes of the wrapper function that are updated with respect to original function attributes as a tuple(optional argument).
To understand this method better, let us observe a few cases of using decorators and partials in Python.
Example 1:
Python3
# Defining the decorator def hi(func): def wrapper(): "Hi has taken over Hello Documentation" print ("Hi Lazyroar") func() return wrapper @hi def hello(): "this is the documentation of Hello Function" print ("Hey Geeks") # Driver Code print (hello.__name__) print (hello.__doc__) help (hello) |
Output:
wrapper Hi has taken over Hello Documentation Help on function wrapper in module __main__: wrapper() Hi has taken over Hello Documentation
In the above example, when we use the decorator function hi and use its wrapper to wrap over hello, the module level constants of function hello, such as __name__, __doc__ etc., are replaced by those of the wrapper in function hi. The same situation arises while using partials from the functools module. Let us see an example :
Example 2:
Python3
import functools def divide(a, b): "Original Documentation of Divide" return a / b half = functools.partial(divide, b = 2 ) oneThird = functools.partial(divide, b = 3 ) try : print (half.__name__) except AttributeError: print ( 'No Name' ) print (half.__doc__) |
Output:
No Name partial(func, *args, **keywords) – new function with partial application of the given arguments and keywords.
In the above example, half and oneThird have no __name__(throws an AttributeError when referenced) since they have been created through partials. As their documentation, they inherit the documentation of the partial method. The above scenarios are inherently problematic because the module level constants serve important purposes for identifying, managing and retrieving content. Metadata is extremely important for these reasons as well as for tracking the usage of the content. Hence if one is trying to create an API or libraries, it will not be user-friendly because help(function) would not return meaningful information about how to use its methods. The help(function) would return the wrapper function’s documentation which would be confusing to the user. This problem can be easily solved by @functools.update_wrapper(). Let us consider the first example. We can use update_wrapper() in the following way:
Example 1:
Python3
# Python program to demonstrate # update)wrapper() method import functools as ft # Defining the decorator def hi(func): def wrapper(): "Hi has taken over Hello Documentation" print ("Hi Lazyroar") func() # Note The following Steps Clearly print ("UPDATED WRAPPER DATA") print (f 'WRAPPER ASSIGNMENTS : {ft.WRAPPER_ASSIGNMENTS}' ) print (f 'UPDATES : {ft.WRAPPER_UPDATES}' ) # Updating Metadata of wrapper # using update_wrapper ft.update_wrapper(wrapper, func) return wrapper @hi def hello(): "this is the documentation of Hello Function" print ("Hey Geeks") print (hello.__name__) print (hello.__doc__) help (hello) |
Output:
UPDATED WRAPPER DATA WRAPPER ASSIGNMENTS : (‘__module__’, ‘__name__’, ‘__qualname__’, ‘__doc__’, ‘__annotations__’) UPDATES : (‘__dict__’, ) hello this is the documentation of Hello Function Help on function hello in module __main__: hello() this is the documentation of Hello Function
By using update_wrapper() we see that the function hello retains its original metadata. Similarly, let us examine the second example, but this time we shall use update_wrapper()
Python3
# Python program to demonstrate # update)wrapper() method import functools def divide(a, b): "Original Documentation of Divide" return a / b half = functools.partial(divide, b = 2 ) oneThird = functools.partial(divide, b = 3 ) print ("UPDATED WRAPPER DATA") print (f 'WRAPPER ASSIGNMENTS : {functools.WRAPPER_ASSIGNMENTS}' ) print (f 'UPDATES : {functools.WRAPPER_UPDATES}' ) # Updating Metadata of wrapper # using update_wrapper ft.update_wrapper(half, divide) try : print (half.__name__) except AttributeError: print ( 'No Name' ) print (half.__doc__) help (half) help (oneThird) |
Output:
UPDATED WRAPPER DATA WRAPPER ASSIGNMENTS : (‘__module__’, ‘__name__’, ‘__qualname__’, ‘__doc__’, ‘__annotations__’) UPDATES : (‘__dict__’,) divide Original Documentation of Divide Help on partial in module __main__ object: divide = class partial(builtins.object) | partial(func, *args, **keywords) – new function with partial application | of the given arguments and keywords. | | Methods defined here: | | __call__(self, /, *args, **kwargs) | Call self as a function. | | __delattr__(self, name, /) | Implement delattr(self, name). | | __getattribute__(self, name, /) | Return getattr(self, name). | | __new__(*args, **kwargs) from builtins.type | Create and return a new object. See help(type) for accurate signature. | | __reduce__(…) | helper for pickle | | __repr__(self, /) | Return repr(self). | | __setattr__(self, name, value, /) | Implement setattr(self, name, value). | | __setstate__(…) | | ———————————————————————- | Data descriptors defined here: | | __dict__ | | args | tuple of arguments to future partial calls | | func | function object to use in future partial calls | | keywords | dictionary of keyword arguments to future partial calls Help on partial object: class partial(builtins.object) | partial(func, *args, **keywords) – new function with partial application | of the given arguments and keywords. | | Methods defined here: | | __call__(self, /, *args, **kwargs) | Call self as a function. | | __delattr__(self, name, /) | Implement delattr(self, name). | | __getattribute__(self, name, /) | Return getattr(self, name). | | __new__(*args, **kwargs) from builtins.type | Create and return a new object. See help(type) for accurate signature. | | __reduce__(…) | helper for pickle | | __repr__(self, /) | Return repr(self). | | __setattr__(self, name, value, /) | Implement setattr(self, name, value). | | __setstate__(…) | | ———————————————————————- | Data descriptors defined here: | | __dict__ | | args | tuple of arguments to future partial calls | | func | function object to use in future partial calls | | keywords | dictionary of keyword arguments to future partial calls
In this example, we see that half inherits the basic module-level constants of the function divide. When we use help(half), we see that it is partially derived from the divide. It is worth noting that such is not the case for oneThird, since help(oneThird) does not tell us the parent function. Therefore, through the above illustrations, we can understand the use cases for the update_wrapper(…) method provided in the functools module. In order to preserve the metadata of a function for further use, update_wrapper(…) is a powerful tool that can be used without much hassle. The method, therefore, proves extremely useful in cases of decorators and partials.