Related Articles

Related Articles

Python Functools – update_wrapper()
  • Last Updated : 11 May, 2020

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:

  1. wrapper: A wrapper function.
  2. wrapped: The function being wrapped over or the wrapped function.
  3. assigned: Attributes of the wrapped function which are assigned to the matching attributes of wrapper function as a tuple(optional argument).
  4. 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:

filter_none

edit
close

play_arrow

link
brightness_4
code

# Defining the decorator
def hi(func):
  
    def wrapper():
        "Hi has taken over Hello Documentation"
        print("Hi geeks")
        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)

chevron_right


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:

filter_none

edit
close

play_arrow

link
brightness_4
code

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

chevron_right


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:

filter_none

edit
close

play_arrow

link
brightness_4
code

# Python program to demonstrate
# ipdate)wrapper() method
  
  
import functools as ft
  
  
# Defining the decorator
def hi(func):
      
    def wrapper():
        "Hi has taken over Hello Documentation"
        print("Hi geeks")
        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)

chevron_right


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

filter_none

edit
close

play_arrow

link
brightness_4
code

# Python program to demonstrate
# ipdate)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)

chevron_right


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.

Attention geek! Strengthen your foundations with the Python Programming Foundation Course and learn the basics.

To begin with, your interview preparations Enhance your Data Structures concepts with the Python DS Course.

My Personal Notes arrow_drop_up
Recommended Articles
Page :