В библиотеке декоратор заменяет функцию обёрткой, из за чего теряются её имя и документация. Какой стандарт...

В библиотеке декоратор заменяет функцию обёрткой, из-за чего теряются её имя и документация. Какой стандартный механизм Python сохраняет эти метаданные?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Используйте functools.wraps при создании обёртки. Этот механизм копирует основные метаданные исходной функции и устанавливает ссылку wrapped, благодаря чему инструменты интроспекции могут распознать исходную функцию.

Исторический контекст

Декоратор обычно возвращает новый вызываемый объект — обёртку. Такая замена удобна для добавления логирования, проверки доступа или измерения времени, но сама по себе не переносит имя, документацию и другие атрибуты исходной функции.

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

Постановка проблемы

Без сохранения метаданных у обёртки могут отображаться имя вроде wrapper, пустая документация и некорректная информация при трассировке ошибок. Это ухудшает диагностику и может ломать инструменты, которые анализируют функции через inspect.

Ручное присваивание отдельных атрибутов ненадёжно: легко забыть важное поле, а набор поддерживаемых метаданных может зависеть от версии Python.

Подробное решение

functools.wraps — это декоратор для самой внутренней функции-обёртки. Он делегирует работу functools.update_wrapper, который по умолчанию переносит основные атрибуты исходной функции, обновляет словарь атрибутов обёртки и устанавливает __wrapped__.

from functools import wraps def logged(func): @wraps(func) def wrapper(*args, **kwargs): print("вызов") return func(*args, **kwargs) return wrapper @logged def add(a, b): """Складывает два числа.""" return a + b print(add.__name__) # add print(add.__doc__) # Складывает два числа.

После применения декоратора имя add всё равно связано с объектом-обёрткой, а не с исходной функцией. Однако wraps переносит её __name__, __qualname__, __doc__, аннотации и другие стандартные метаданные; точный набор зависит от версии Python.

Атрибут wrapped особенно важен: например, inspect.signature обычно использует его, чтобы показать сигнатуру исходной функции. При этом wraps не превращает обёртку в исходный объект, не копирует замыкание и не устраняет побочные эффекты, добавленные декоратором.

Если нужно настроить перенос атрибутов, применяют functools.update_wrapper напрямую либо передают параметры assigned и updated в wraps. Полагаться только на __name__ недостаточно: документация, аннотации и цепочка __wrapped__ имеют отдельное практическое значение.

Ситуация из практики

В библиотеке автор добавил декоратор проверки прав доступа. Первый вариант вручную присваивал обёртке имя и документацию. Плюс такого решения — простота для одного атрибута; минусы — риск неполного переноса метаданных и отсутствие корректной связи через __wrapped__.

Второй вариант не переносил метаданные вообще. Он требовал меньше кода, но в документации библиотеки все методы отображались как wrapper, а анализ сигнатур давал сведения о *args и **kwargs.

Выбран functools.wraps: он решает задачу стандартным способом, сохраняет совместимость с инструментами интроспекции и не мешает обёртке выполнять проверку доступа. При этом разработчик отдельно учёл, что сам декоратор всё равно может менять поведение функции и сигнатуру вызова.

Что кандидаты часто упускают

  1. Зачем нужен __wrapped__, если имя и документация уже скопированы?

    Одного копирования отображаемых атрибутов недостаточно для восстановления структуры декораторов. __wrapped__ связывает обёртку с обёрнутой функцией, поэтому inspect.signature и другие инструменты могут пройти по цепочке декораторов и получить исходную сигнатуру или исходный объект.

  2. Сохраняет ли wraps исходное поведение и идентичность функции?

    Нет. Имя функции после декорирования обычно указывает на обёртку, а вызов проходит через её код. wraps сохраняет метаданные и ссылку на исходную функцию, но не отменяет добавленную логику, побочные эффекты, изменение аргументов или изменение возвращаемого значения.

  3. Почему иногда одной wraps недостаточно для корректной сигнатуры?

    wraps помогает inspect увидеть исходную сигнатуру через __wrapped__, но фактический вызов всё равно принимает параметры, объявленные обёрткой. Если декоратор намеренно меняет контракт функции, нужно явно описать новую сигнатуру, например через атрибут __signature__, либо реализовать обёртку с соответствующим интерфейсом. Автоматическое копирование метаданных не меняет правила связывания аргументов Python.