Шаг 157.
Python: сборник рецептов. Метапрограммирование. Сохранение метаданных функции при написании декораторов

    На этом шаге мы рассмотрим использование декоратора @wraps из библиотеки functools.

Задача

    Вы написали декоратор, но когда вы применяете его к функции, теряются важные метаданные, такие как имя, строка документации (doc string), аннотации и сигнатура вызова.

Решение

    При определении декоратора не забывайте применить декоратор @wraps из библиотеки functools к функции-обертке. Например:

>>> import time
>>> from functools import wraps
>>> def timethis(func):
    '''
    Декоратор, который показывает время выполнения.
    '''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

>>> 
>>> @timethis
def countdown(n:int):
        '''
        Counts down
        '''
        while n > 0:
            n -= 1

            
>>> countdown(100000)
countdown 0.011038780212402344
>>> print('Name:', countdown.__name__)
Name: countdown
>>> print('Docstring:', repr(countdown.__doc__))
Docstring: '\n        Counts down\n        '
>>> print('Annotations:', countdown.__annotations__)
Annotations: {'n': <class 'int'>}
>>> 


Обсуждение

    Копирование метаданных декоратора - это важный аспект. Если вы забудете применить @wraps, то обнаружите, что декорированная функция потеряла различную полезную информацию. Например, если декоратор @wraps в последнем примере был бы опущен, метаданные выглядели бы так:

>>> countdown.__name__
'wrapper'
>>> countdown.__doc__
>>> countdown.__annotations__
{}
>>>

    Важная особенность декоратора @wraps заключается в том, что он делает обернутую функцию доступной в атрибуте __wrapped__. Например, если вы хотите напрямую обратиться к обернутой функции, то можете сделать так:

>>> countdown.__wrapped__(100000)
>>>

    Наличие атрибута __wrapped__ также позволяет декорированным функциям правильно показывать сигнатуры обернутых функций. Например:

>>> from inspect import signature
>>> print(signature(countdown))
(n: int)
>>> 

    Иногда возникает вопрос о том, как написать декоратор, который прямо копирует изначальную сигнатуру вызова обернутой функции (в противоположность подходу с использованием *args и **kwargs). В общем случае этого трудно будет добиться без некоторой акробатики с генератором строк кода и функцией exec(). Если честно, обычно лучше использовать @wraps и полагаться на тот факт, что сигнатура изначальной функции может быть получена через атрибут __wrapped__.

    На следующем шаге мы рассмотрим снятие ("разворачивание") декоратора.




Предыдущий шаг Содержание