В конце прошлого урока мы наткнулись на неприятность: декорированная функция теряет своё нормальное имя и превращается в безликую wrapper. Аналогия тут простая: представь, что ты отправил человеку письмо, а на конверте вместо имени получателя написал "тот, кто живёт в этом доме". Формально письмо дойдёт, но идентифицировать получателя стало сложнее. Вот с декорированной функцией то же самое: она продолжает работать, но теряет свою "подпись".
Вывод:
wrapper
None
Смотри, что мы потеряли. Имя функции стало wrapper вместо add. А docstring - строка документации """Складывает два числа.""" - вообще пропала, стала None. И это не мелочь для галочки. Это реально влияет на работу инструментов: help(add) покажет бесполезную информацию про wrapper, автодокументация (Sphinx и подобные) сгенерирует неправильные описания, отладчик в стектрейсе покажет имя wrapper вместо осмысленного add, а инструменты вроде IDE, которые подсказывают сигнатуру функции при наборе кода, собьются с толку.
В стандартной библиотеке для этого есть готовый инструмент, functools.wraps. Сам он тоже декоратор, просто применяется не к твоей бизнес-функции, а к обёртке внутри твоего декоратора:
Вывод:
add2
Складывает два числа.
functools.wraps(func) копирует на wrapper служебные атрибуты оригинальной функции: __name__, __doc__, __module__, __qualname__ и некоторые другие. Снаружи всё выглядит так, будто декорирования как будто и не было - имя и документация на месте, а поведение всё равно расширено.
functools.wraps даёт ещё одну полезную вещь - атрибут __wrapped__, который хранит ссылку на исходную, недекорированную функцию:
Вывод:
<function add2 at 0x1052c3530>
(a, b)
Модуль inspect умеет "смотреть сквозь" __wrapped__ и достаёт настоящую сигнатуру (a, b), а не (*args, **kwargs) от обёртки. Это то, что реально используют внешние инструменты: автогенераторы документации, статические анализаторы, дебаггеры.
Это классический вопрос на code review: "вот декоратор без functools.wraps, что с ним не так". Правильный ответ не просто "надо добавить functools.wraps", а объяснение, что именно ломается без него: метаданные функции (имя, докстринг, сигнатура), от которых зависят инструменты интроспекции, тестирования и документации. Хорошее эмпирическое правило: если пишешь декоратор, который не является строго одноразовым внутренним хаком, всегда добавляй @functools.wraps(func) над wrapper. Это стоит одну строчку и почти никогда не имеет недостатков.
functools.wraps декорированная функция теряет __name__, __doc__ и правильную сигнатуру - снаружи она выглядит как wrapper, а не как исходная функция.@functools.wraps(func) ставится над определением wrapper внутри декоратора, а не над самой декорируемой функцией.functools.wraps даёт побочный бонус - атрибут __wrapped__, который хранит ссылку на исходную функцию и используется, например, модулем inspect.functools.wraps, это дешёвая защита от неприятностей с отладкой и интроспекцией.Дальше усложняем: что если декоратору самому нужны параметры, например @retry(times=3). Для этого понадобится ещё один уровень вложенности функций.