Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Functools: Wrappers

  • Exclusively used when working with decorators.

import time


def print_time(f):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = f(*args, **kwargs)
        print(f'function {f.__name__} took {
              time.time() - start_time:.2f} seconds to execute')
        return result
    return wrapper


def perfect_function():
    """This is a perfect docstring"""
    time.sleep(1)
    print("Finished being perfect")

Without wraps, this would happen:

>>> perfect_function = print_time(perfect_function)
>>> perfect_function.__doc__

>>> perfect_function.__name__
'wrapper'

It's because the decorator print_time(f) is replacing the perfect function with the wrapper.

Here are some simple changes you can make to fix this:


from functools import wraps
import time


def print_time(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = f(*args, **kwargs)
        print(f'function {f.__name__} took {
              time.time() - start_time:.2f} seconds to execute')
        return result
    return wrapper


@print_time
def perfect_function():
    """This is a perfect docstring"""
    time.sleep(1)
    print("Finished being perfect")

Under the hood, this automatically does perfect_function = print_time(perfect_function).

And now run:


>>> perfect_function.__doc__
'This is a perfect docstring'
>>> perfect_function.__name__
'perfect_function'
>>> perfect_function()
Finished being perfect
function perfect_function took 1.00 seconds to execute

This is useful when writing an API or a library, and someone wants to know what your function does and it's name. Instead of always showing the wrapper's name and docstring, it will point to the accurate one.