Допустим, декоратор задаёт собственный атрибут обёртки до применения functools.wraps. Каким станет этот атрибут после применения wraps?
Если исходная функция имеет атрибут с таким же именем, значение атрибута обёртки будет заменено значением исходной функции. Если одноимённого атрибута у исходной функции нет, уже заданный атрибут обёртки обычно сохранится.
Декоратор заменяет исходную функцию другим объектом, поэтому без специальных действий теряются её имя, документация, аннотации и пользовательские атрибуты. Это ухудшает работу отладчиков, документации, средств анализа и кода, использующего интроспекцию.
Для решения этой проблемы в стандартной библиотеке используется механизм functools.update_wrapper, а functools.wraps является удобной фабрикой декоратора для его применения.
Обёртка может иметь собственное состояние и служебные атрибуты. Однако применение wraps не только устанавливает стандартные метаданные, но и обновляет словарь атрибутов обёртки данными исходной функции.
Поэтому конфликт имён разрешается не в пользу обёртки: значение из словаря исходной функции перезаписывает одноимённое значение, заданное заранее у обёртки. Ошибка в ожиданиях может привести к потере настроек, например счётчика, флага или идентификатора, записанного в обёртку до вызова wraps.
Упрощённо wraps выполняет две операции. Сначала он копирует стандартные атрибуты исходной функции, включая имя, квалифицированное имя, документацию и аннотации. Затем он обновляет словарь пользовательских атрибутов обёртки содержимым словаря исходной функции.
Ключевой момент — операция обновления словаря: при совпадении ключей последнее присвоенное значение заменяет предыдущее. Поэтому атрибут, существовавший у обёртки до применения wraps, будет перезаписан, если такой же атрибут есть у исходной функции.
В реальном примере перед использованием функции нужно импортировать functools. Если атрибут role отсутствует у исходной функции, операция обновления словаря его не удаляет, поэтому значение wrapper сохранилось бы.
Копирование пользовательских атрибутов выполняется поверхностно. Сама ссылка на изменяемый объект, например список или словарь, может быть перенесена в словарь обёртки, но вложенные объекты не копируются независимо.
Если служебный атрибут должен иметь значение именно у обёртки, его следует задавать после применения wraps либо выбрать имя, которого нет у исходной функции. Это сохраняет преимущества интроспекции, но явно задаёт приоритет метаданных обёртки.
В системе наблюдаемости декоратор создавал обёртку с атрибутом metric_name, содержащим имя метрики. Затем разработчик применил functools.wraps. У части исходных функций уже существовал пользовательский атрибут metric_name, и декоратор начал использовать его значение вместо рассчитанного имени.
Рассматривались два варианта. Можно было отказаться от wraps, но тогда ухудшались имя функции, документация и поддержка инструментов анализа. Можно было переименовать внутренний атрибут, но это требовало изменений в коде, читающем состояние обёртки.
Выбрали третий вариант: сначала применять wraps, а затем задавать metric_name на обёртке. В результате стандартные метаданные сохранялись, а служебное значение декоратора имело гарантированный приоритет. Дополнительный компромисс — использовать отдельный префикс для внутренних атрибутов, чтобы снизить вероятность конфликтов.
Копирует ли wraps замыкание исходной функции?
Нет. wraps не переносит локальные переменные, ячейки замыкания или состояние исходной функции. Обёртка продолжает замыкать те объекты, которые были захвачены при её создании, а исходная функция обычно сохраняется в её служебной связи __wrapped__.
Поэтому wraps восстанавливает метаданные и связь для интроспекции, но не превращает обёртку в копию исходной функции и не переносит её внутреннее состояние.
Что произойдёт, если задать атрибут обёртки после применения wraps?
Последующее присваивание заменит значение, скопированное из исходной функции. Это стандартный способ явно установить служебный атрибут декоратора после сохранения метаданных.
Такой порядок следует применять осознанно: он решает конфликт имён, но может скрыть пользовательский атрибут исходной функции от кода, который ожидает найти именно его через обёртку.
Создаёт ли wraps независимую копию изменяемых пользовательских атрибутов?
Нет. Обновление словаря атрибутов является поверхностным: если исходная функция содержит список, словарь или другой изменяемый объект, обёртка получит ссылку на этот же объект.
Изменение содержимого такого объекта через обёртку будет видно через исходную функцию и наоборот. Если нужна независимость состояния, объект необходимо копировать отдельно, например с помощью подходящей операции копирования, учитывая её стоимость и структуру данных.