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.

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

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.

update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
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__)

Examples with functools wrappers

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

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

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.