Короткий ответ
Декоратор оборачивает исходную функцию во внутреннюю функцию-обёртку, которая выполняет замер времени до и после вызова оригинала; без functools.wraps обёрнутая функция теряет своё имя, docstring и другие метаданные, так как снаружи видна только функция-обёртка, а не оригинал.
Как это работает подробнее
Декоратор @my_decorator синтаксически эквивалентен func = my_decorator(func) — имя func в месте использования начинает указывать на объект, который вернул декоратор (обычно это wrapper-функция). functools.wraps(func) — это сам декоратор, применяемый к wrapper, который копирует __name__, __doc__, __module__ и прочие атрибуты оригинальной функции на wrapper, чтобы снаружи decorated-функция выглядела как исходная.
Пример кода
example.py
import time
import functools
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} выполнена за {elapsed:.4f} с")
return result
return wrapper
@timed
def compute_sum(n):
"""Считает сумму чисел от 0 до n."""
return sum(range(n))
compute_sum(1_000_000)
print(compute_sum.__name__, "-", compute_sum.__doc__) # compute_sum - Считает сумму...Что использовать на практике
- Практика. Без
functools.wrapsинструменты автодокументации, отладчики, логирование стектрейсов иhelp(func)показывают имяwrapperвместо реального имени функции, что затрудняет диагностику в продакшене. Это особенно критично, если декораторы применяются массово (логирование, кеширование, авторизация) на многих функциях API.
Подводные камни
- Риски. Забывают
*args, kwargsв сигнатуре wrapper, из-за чего декоратор перестаёт работать с функциями, принимающими аргументы. Также забывают вернуть результат вызова оригинальной функции из wrapper, из-за чего декорированная функция всегда возвращает None.