The functools module in the standard library provides  several tools for  manipulating and extending functions. 

The update_wrapper() function in the module is used to update the properties of a wrapper function so that it is indistinguishable from the wrapped function. The function allows the wrapper to be used similarly to the wrapped function, while preserving metadata associated with the original function, such as its __name____doc__, __module__, etc. This ensures that the wrapper function will interact seamlessly with other code expecting the original function.

By default wrapper functions do not inherit properties of the wrapped functions.

ExampleEdit & Run
def addition_decorator(func):
    def wrapper(a, b):
        return "%s + %s = %s"%(a, b, str(func(a, b)))

    return wrapper

@addition_decorator
def add(a, b):
    '''calculates the arithmetic sum of a and b'''
    return a + b

print(add(60, 50))

#the wrapper does not inherit the properties of the wrapped function
print(add.__doc__) #docstring
print(add.__name__) #name
copy
Output:
60 + 50 = 110 None wrapper [Finished in 0.01059269206598401s]

As you can see above, the properties being returned are of the wrapper function rather than the original.

How the update_wrapper function works

As mentioned earlier, the update_wrapper() function makes the wrapper function indistinguishable from the original function. It assigns the necessary properties to the wrapper  function, such as __name__, __doc__, __module____dict__,  etc.

Syntax:
update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
copy
ExampleEdit & Run
from functools import update_wrapper

def addition_decorator(func):
    def wrapper(a, b):
        return "%s + %s = %s"%(a, b, str(func(a, b)))

    #update the wrapper
    update_wrapper(wrapper, func)
    return wrapper

@addition_decorator
def add(a, b):
    '''calculates the arithmetic sum of a and b'''

    return a + b

print(add(60, 50))


print(add.__doc__)
print(add.__name__)
copy
Output:
60 + 50 = 110 calculates the arithmetic sum of a and b add [Finished in 0.013160275761038065s]

Examples with functools wrappers

The functools module provides various wrapper functions which we can use to demonstrate further how the update_wrapper() function works.

ExampleEdit & Run
from functools import partial, update_wrapper

L = [-2, 0, 1, -3, 4, -6, 5]

abs_sorted = partial(sorted, key = abs)

#before updating the wrapper
print('before updating wapper:')
print(abs_sorted(L))
print(abs_sorted.__doc__, '\n')

update_wrapper(abs_sorted, sorted)
#after updating the wrapper
print('after updating wrapper:')
print(abs_sorted(L))
print(abs_sorted.__doc__)
print(abs_sorted.__name__, '\n')
copy
Output:
before updating wapper: [0, 1, -2, -3, 4, 5, -6] partial(func, *args, **keywords) - new function with partial application     of the given arguments and keywords.   after updating wrapper: [0, 1, -2, -3, 4, 5, -6] Return a new list containing all items from the iterable in ascending order. A custom key function can be supplied to customize the sort order, and the reverse flag can be set to request the result in descending order. sorted  [Finished in 0.012801114935427904s]

The partial class  in the module is used to create partial functions which creates versions of a function in which some arguments are already pre-filled. We used it above to create a version of the builtin sorted() function in which elements are sorted by their absolute value by default. We then used the update_wrapper() function to update the properties of the newly created abs_sorted function so that it matches those of the original sorted() function.