После применения декоратора с каким объектом связывается имя функции?
После применения декоратора имя функции связывается не обязательно с исходной функцией, а с объектом, который вернул декоратор. Декоратор получает исходную функцию как аргумент, выполняется во время создания определения функции, а его результат присваивается имени функции.
Декораторы появились как единый синтаксис для распространённой задачи: изменить или обернуть функцию сразу при её объявлении. До этого такую замену приходилось выполнять отдельным присваиванием, что хуже показывало связь между исходной функцией и применяемым поведением.
Подход поддерживает композицию: несколько декораторов можно применять последовательно, формируя цепочку обёрток. Это удобно для логирования, контроля доступа, кэширования и регистрации обработчиков.
Внешне имя функции может выглядеть как ссылка на объявленную функцию, но после декорирования оно обычно указывает на другую функцию-обёртку. Если этого не учитывать, можно неправильно интерпретировать результат проверки идентичности, имя функции, трассировку стека или доступ к атрибутам.
Особенно опасно забывать, что декоратор выполняется при выполнении определения функции, а не при каждом вызове. Ошибка в декораторе поэтому проявится уже во время импорта модуля или другого выполнения объявления функции.
Механизм состоит из трёх этапов:
Если декоратор возвращает обёртку, вызов имени функции запускает обёртку. Она может выполнить дополнительную логику, вызвать исходную функцию или вообще не вызывать её. Если декоратор ничего не возвращает, имя будет связано со значением None, и последующий вызов приведёт к ошибке.
После применения @log_call имя add указывает на wrapper. Декоратор wraps копирует важные метаданные исходной функции, включая имя и документацию, но не возвращает исходный объект и не отменяет сам факт обёртывания.
Порядок нескольких декораторов читается сверху вниз, но применяется снизу вверх: ближайший к объявлению получает исходную функцию первым. Это позволяет строить вложенную цепочку, однако порядок может влиять на результат и побочные эффекты.
В веб-приложении нужно одновременно проверять права доступа и измерять время выполнения обработчика. Возможны два варианта: вручную создавать вложенные функции в каждом обработчике или использовать декораторы. Ручной подход не требует отдельного соглашения, но приводит к дублированию и неоднородному коду.
Выбран вариант с двумя декораторами и functools.wraps. Он централизует общую логику, сохраняет метаданные обработчиков и позволяет менять порядок проверок явно. При этом разработчики должны документировать порядок декораторов и не скрывать в них неожиданные побочные эффекты.
1. Декоратор вызывается при каждом вызове функции или только один раз?
Декоратор вызывается один раз в момент выполнения объявления функции. При обычном последующем вызове выполняется уже результат декоратора — например, созданная им обёртка. Поэтому дорогостоящая настройка внутри тела декоратора не повторяется для каждого вызова, если только сам декоратор специально не создаёт её там.
2. Что произойдёт, если декоратор не вернёт функцию?
Имя функции будет связано с возвращённым значением, например с None. Объявление завершится, но попытка вызвать это имя приведёт к TypeError, поскольку значение не является вызываемым объектом. Это частая ошибка при написании декоратора: обёртка создана, но отсутствует return wrapper.
3. Зачем использовать functools.wraps, если обёртка и так работает?
Без wraps технические атрибуты имени и документации будут относиться к обёртке, а не к исходной функции. Это ухудшает диагностику, документацию и работу инструментов интроспекции. wraps не восстанавливает исходную идентичность объекта, но сохраняет его важные метаданные и добавляет ссылку на исходную функцию через __wrapped__.