Декораторы
Декоратор в Python не является особой синтаксической конструкцией со своей семантикой — это обычный вызов обычной функции, записанный короче. Строка @dec над def f разворачивается ровно в f = dec(f): интерпретатор создаёт функцию, передаёт объект функции в dec и связывает имя f с тем, что dec вернул. Всё поведение декораторов выводится из этой одной подстановки, и почти каждая ошибка на собеседовании — следствие того, что кандидат её не проговорил.
Из подстановки сразу следуют три неочевидных факта. Во-первых, декоратором может быть любой вызываемый объект — функция, lambda, класс, экземпляр с __call__, — а декорировать можно и функцию, и метод, и класс. Во-вторых, возвращённая обёртка — это уже другой объект, поэтому __name__, __doc__ и __qualname__ подменяются, и без functools.wraps ломается интроспекция. В-третьих, декоратор работает в момент определения, а не вызова, — и когда декоратору нужен параметр, требуется третий уровень вложенности, потому что @foo() сначала вызывает foo, а уже результат применяет к функции. Разберите каждый механизм в слоях ниже.
Карта темы
- Декоратор и сахар @ —
@decнадdef fэто в точностиf = dec(f); всё остальное следует отсюда. - Любой вызываемый объект — декоратором служит функция,
lambda, класс или экземпляр с__call__, а целью — функция, метод или класс. - functools.wraps и метаданные — обёртка подменяет
__name__,__doc__,__qualname__;wrapsкопирует их обратно и ставит__wrapped__. - Фабрика декораторов —
@foo()вызываетfooпервым, поэтому параметризованный декоратор требует трёх уровней вложенности. - Порядок применения стека — стек декораторов применяется снизу вверх, а выполняется при вызове сверху вниз.
- @repeat — повтор и замер — прикладная фабрика, соединяющая три уровня,
wraps,*args/**kwargsиtime.perf_counter.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Считать, что @dec вызывает f и кэширует результат | Декоратор выполняется один раз при определении и возвращает объект, а не значение вызова |
Думать, что @dec — «пометка» и f остаётся прежней | Имя f перепривязано к результату dec(f); исходная функция доступна только через замыкание или __wrapped__ |
| Писать параметризованный декоратор в два уровня | @foo('x') вызовет foo('x') и применит его результат — двух уровней не хватает, нужен третий |
Забыть @functools.wraps(func) на обёртке | __name__ становится wrapper, теряются docstring и сигнатура — ломаются help, трейсбеки и автодокументация |
Читать стек @a / @b сверху вниз как порядок применения | Применение идёт снизу вверх — a(b(f)); сверху вниз идёт лишь порядок входа в обёртки при вызове |
Не пробрасывать *args, **kwargs через обёртку | Обёртка работает только с той сигнатурой, которую случайно угадал автор, и падает на любом другом вызове |
Считать декоратором только функцию, определённую через def | Класс с __call__ и lambda — такие же валидные декораторы; ограничение только одно — объект должен быть вызываемым |
Значение для собеседований
Тема входит в обязательный минимум для middle-разработчика, и проверяют её почти всегда одинаково. Сначала просят объяснить, во что разворачивается @dec — ответ «это f = dec(f)» закрывает половину дальнейших вопросов, а ответ «это пометка для интерпретатора» сразу переводит разговор в разбор ошибок. Затем дают написать декоратор замера времени: здесь смотрят на *args, **kwargs, на return результата и на @wraps. Забытый wraps — самая частая претензия, потому что она видна снаружи: f.__name__ покажет wrapper.
Дальше идут два вопроса на понимание времени выполнения. Первый — разница @foo и @foo(): кандидат должен назвать три уровня вложенности и объяснить, почему их именно три. Второй — порядок стека: по коду с двумя декораторами просят предсказать результат, и правильный ответ отличает порядок применения (снизу вверх) от порядка исполнения обёрток при вызове (сверху вниз). Типичная ошибка — назвать один порядок для обоих. Если тема идёт глубже, спрашивают про декорирование классов и про декоратор-класс с __call__, а также про __wrapped__ и inspect.signature — это уже проверка на то, работали ли вы с интроспекцией руками.