Декораторы
Декораторы функций и классов, замыкания и сохранение метаданных.
8 вопросов
JuniorТеорияОчень частоЧто такое декоратор, и во что разворачивается @dec?
Что такое декоратор, и во что разворачивается @dec?
Декоратор — это вызываемый объект, который принимает функцию и возвращает другую (обычно оборачивающую) функцию, расширяя поведение без правки оригинала. @dec над def f — это сахар для f = dec(f).
Типичные ошибки
- ✗Думать, что
@decсразу вызываетf, а не переназначаетfнаdec(f) - ✗Считать, что декоратор не может вернуть новую функцию, заменяющую оригинал
- ✗Забывать, что последующие вызовы идут в возвращённую обёртку, а не в оригинал
Уточняющие вопросы
- →Что может быть декоратором, кроме обычной функции через
def? - →Когда именно выполняется декоратор — при определении или при вызове?
JuniorТеорияЧастоЧто может быть декоратором, и к чему его можно применять?
Что может быть декоратором, и к чему его можно применять?
Декоратором может быть любой вызываемый объект: функция, lambda, класс или экземпляр с __call__. Применять его можно к функциям, методам или классам — к чему угодно под синтаксисом @. Главное — чтобы цель была вызываемой.
Типичные ошибки
- ✗Считать, что годятся только функции через
def, забывая классы и__call__ - ✗Полагать, что декораторы применимы лишь к функциям и не могут декорировать класс
- ✗Забывать, что
lambdaсама по себе — допустимый вызываемый декоратор
Уточняющие вопросы
- →Как экземпляр становится вызываемым, чтобы работать декоратором?
- →Что возвращает декоратор класса, и что заменяет имя класса?
MiddleТеорияЧастоВ чём разница между @foo и @foo()?
В чём разница между @foo и @foo()?
@foo применяет foo прямо к функции: f = foo(f). @foo() сначала ВЫЗЫВАЕТ foo() — здесь foo это фабрика декораторов, возвращающая сам декоратор, который затем применяется: f = foo()(f). Вызов фабрики даёт параметризацию.
Типичные ошибки
- ✗Считать
@fooи@foo()взаимозаменяемыми, хотя подходит лишь один из них - ✗Думать, что
@foo()вызывает декорируемуюf, а не фабрикуfoo - ✗Забывать, что для
@foo()нужно, чтобыfooвернула декоратор, а не обёртку
Уточняющие вопросы
- →Сколько вложенных уровней функций нужно фабрике параметризованного декоратора?
- →Что возвращает
foo(), чтобы результат сам был корректным декоратором?
MiddleТеорияЧастоЗачем применять functools.wraps к оборачивающей функции?
Зачем применять functools.wraps к оборачивающей функции?
Без него обёртка подменяет оригинал, и __name__, __doc__, __qualname__ становятся обёрточными — ломая интроспекцию, help и трассировки. @wraps(func) копирует эти метаданные на обёртку и ставит __wrapped__ в оригинал.
Типичные ошибки
- ✗Опускать
@wraps(func)и потом недоумевать над именем обёртки в трассировках - ✗Думать, что
wrapsвлияет на скорость, а не только на метаданные - ✗Считать
wrapsобязательным для работы декоратора, а не для интроспекции
Уточняющие вопросы
- →Что позволяет восстановить атрибут
__wrapped__, который ставитwraps? - →Какие dunder-атрибуты
wrapsкопирует по умолчанию, а какие обновляет?
MiddleКодИногдаДекоратор, повторяющий вызов N раз и выводящий общее время
Декоратор, повторяющий вызов N раз и выводящий общее время
Нужны три уровня: repeat(reps) захватывает счётчик и возвращает декоратор; тот берёт func и возвращает обёртку с @wraps(func). Обёртка фиксирует time.perf_counter(), запускает func reps раз (сохраняя последний результат), печатает суммарное время и возвращает этот результат. Слой аргумента нужен для @repeat(3); @wraps сохраняет имя и докстринг.
Типичные ошибки
- ✗Использовать два уровня и не принимать аргумент
repsкорректно - ✗Сбрасывать таймер внутри цикла вместо замера всего прогона
- ✗Опускать
@wraps, теряя имя и докстринг функции
Уточняющие вопросы
- →Почему приём аргумента декоратора требует третьего уровня вложенности?
- →Что ломается для вызывающих, если опустить
@wrapsна обёртке?
MiddleКодИногдаСтек декораторов add_tag('h1') над add_div — реализация и порядок
Стек декораторов add_tag('h1') над add_div — реализация и порядок
add_div оборачивает результат func в <div>...</div>. add_tag(tag) — фабрика: берёт тег и возвращает декоратор, оборачивающий результат в <tag>...</tag>. Стек декораторов применяется снизу вверх — сначала @add_div, затем @add_tag('h1') оборачивает его — поэтому внутренняя разметка это div, а внешняя h1, давая <h1><div>...</div></h1>. Используйте @wraps на каждой обёртке.
Типичные ошибки
- ✗Переворачивать порядок применения, получая
<div><h1>...</h1></div> - ✗Писать
add_tagбез дополнительного слоя-фабрики для аргумента - ✗Забывать
@wraps, теряя метаданные декорируемой функции
Уточняющие вопросы
- →Почему декоратор ближайший к функции (нижний) срабатывает первым?
- →Как определить
add_divкакadd_tag('div'), чтобы убрать дублирование?
MiddleКодИногдаНапишите декоратор, измеряющий время функции
Напишите декоратор, измеряющий время функции
Верните замыкание, которое записывает time.perf_counter() до и после вызова, пробрасывает *args, **kwargs и возвращает результат. Примените @functools.wraps(func) к обёртке, чтобы она сохранила исходные имя и docstring — без wraps декорированная функция теряет эти метаданные.
Типичные ошибки
- ✗Возвращать прошедшее время вместо результата функции
- ✗Опускать
*args/**kwargs, из-за чего обёртка не пробрасывает аргументы - ✗Пропускать
functools.wraps, теряя имя и docstring функции
Уточняющие вопросы
- →Что именно
functools.wrapsкопирует на обёртку? - →Как изменится декоратор, если он должен принимать собственный аргумент, например метку?
SeniorТеорияИногдаКак написать декоратор, принимающий аргумент и сохраняющий метаданные?
Как написать декоратор, принимающий аргумент и сохраняющий метаданные?
Нужны три уровня: внешняя фабрика берёт аргумент и возвращает декоратор, который берёт func и возвращает обёрнутую через @wraps(func) внутреннюю обёртку, использующую аргумент и вызывающую func. Применяется как @factory(arg).
Типичные ошибки
- ✗Брать лишь два уровня и путать аргумент конфигурации с
func - ✗Ставить
@wraps(func)на фабрику или декоратор вместо внутренней обёртки - ✗Пытаться читать аргумент конфигурации из рантайм-
*argsобёртки
Уточняющие вопросы
- →Как один декоратор может поддержать обе формы
@factoryи@factory(arg)? - →Где захватывается аргумент конфигурации — в области какого замыкания?