Как сохранить оригинальное имя функции при использовании декоратора в Python?
Что проверяют
Понимаете ли вы, что декоратор подменяет функцию обёрткой и что при этом теряется. Следом обычно спрашивают, что конкретно копирует wraps и как добраться до исходной функции.
Как отвечать
Без дополнительных мер example.__name__ вернёт wrapper, а __doc__ будет пустым. Стандартное решение: @functools.wraps(func) на внутренней функции. Это сокращение для functools.update_wrapper, и оно делает три вещи:
- копирует атрибуты:
__module__,__name__,__qualname__,__doc__,__annotations__, с Python 3.12 ещё__type_params__; - обновляет
__dict__: атрибуты, которые повесили на исходную функцию, переезжают на обёртку; - добавляет
__wrapped__: ссылку на оригинал. По нейinspect.signatureпоказывает настоящую сигнатуру вместо(*args, **kwargs), аinspect.unwrapснимает все слои декораторов.
У декоратора с параметрами три уровня вложенности, и @wraps(func) ставится на самый внутренний. В декораторе-классе вызывают functools.update_wrapper(self, func) в __init__.
Покажите, где это ломается на практике. Flask строит имя endpoint из __name__, и два вида под одним декоратором без wraps упадут с ошибкой о перезаписи endpoint. FastAPI и pytest читают сигнатуру, чтобы разобрать параметры и фикстуры: без __wrapped__ они видят *args, **kwargs и работают неверно. В тестах func.__wrapped__(...) позволяет вызвать функцию в обход кэша или ретраев.
Если нужна честная сигнатура у самой обёртки, берут библиотеку wrapt: она сохраняет её и корректно работает на методах и классах.
Где спотыкаются
Ставят @wraps на внешнюю функцию декоратора или забывают передать ей func. Метаданные копируются не туда.
Считают, что wraps меняет поведение. Он переносит только атрибуты: обёртка по-прежнему принимает *args, **kwargs, и вернуть результат вызова через return всё так же нужно самим.
Оборачивают async def синхронной функцией. wraps не делает обёртку корутинной: её нужно объявить async def и внутри писать await func(...), иначе inspect.iscoroutinefunction вернёт False, а логика «до и после» отработает до реального выполнения корутины.