На этом шаге мы рассмотрим использование декоратора @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__.
На следующем шаге мы рассмотрим снятие ("разворачивание") декоратора.