Menu
Русский

Декораторы в Python: @-функции, аргументы и functools.wraps

Что такое декораторы Python на самом деле, как написать свой и паттерны (аргументы, стекинг, wraps), ради которых к ним стоит тянуться.

Декоратор — это функция, которая оборачивает функцию

Фраза звучит абстрактно, но механика простая. Декоратор принимает функцию и возвращает функцию. Возвращённая обычно вызывает исходную, накручивая сверху дополнительное поведение.

Самый короткий пример:

main.py
Output
Click Run to see the output here.

shout — декоратор. Он принимает функцию (greet), строит новую функцию (wrapper), которая вызывает оригинал и повышает регистр результата, и возвращает её. Переприсваивание greet = shout(greet) подменяет оригинал на обёрнутую версию.

Этот паттерн переприсваивания настолько частый, что Python дал ему отдельный синтаксис.

@ — сахар для переприсваивания

@name строкой выше def эквивалентно name = name(...) сразу после определения функции:

main.py
Output
Click Run to see the output here.

@shout читается как «примени декоратор shout к этой функции». Python запускает greet = shout(greet) сразу после def — та же механика, меньше набора.

Как увидишь @name — мысленно заменяй на function = name(function). Это всё, что означает синтаксис.

Обработка аргументов

У большинства функций есть аргументы. Пригодный декоратор пробрасывает их дальше. Идиома — *args, **kwargs, способ Python принять любые аргументы: обёртке не должно быть важно, что ждёт обёрнутая функция:

main.py
Output
Click Run to see the output here.

*args забирает все позиционные аргументы. **kwargs — все keyword-аргументы. Обёртка пробрасывает всё в обёрнутую функцию без изменений и делает дополнительную работу, ради которой декоратор и нужен, — здесь поднимает регистр результата.

Такую форму принимает большинство реальных декораторов.

Более полезный пример: тайминг

Выводим, сколько занимает функция:

main.py
Output
Click Run to see the output here.

Паттерн — «запустить что-то до вызова, запустить что-то после» — то, чем в итоге занимается большинство декораторов. Логирование, проверки авторизации, повторы и валидация — одной формы.

Сохраняем оригинал: functools.wraps

Декорирование функции её заменяет, и обёрнутая функция теряет исходные атрибуты __name__ и __doc__:

main.py
Output
Click Run to see the output here.

greet.__name__ теперь "wrapper", docstring исчез. Это ломает help(), трейсбэки и любые инструменты, инспектирующие функцию.

Ремонт — одна строка: @functools.wraps(func) на внутренней функции копирует метаданные.

main.py
Output
Click Run to see the output here.

Всегда ставь @wraps(func) на внутреннюю функцию. Это ничего не стоит и избавляет от удивлённых отладочных сессий позже.

Декораторы с аргументами

Иногда самому декоратору нужна настройка — «повтори функцию до 3 раз», «логируй на уровне DEBUG». Это ещё один слой вложенности: внешняя функция принимает аргументы и возвращает декоратор.

main.py
Output
Click Run to see the output here.

Три слоя звучит как много. Читай изнутри наружу:

  1. repeat(times=3) — вызов функции. Возвращает decorator.
  2. decorator — собственно декоратор, принимает функцию и возвращает обёрнутую.
  3. wrapper — обёрнутая функция, работающая в момент вызова.

Эта форма лежит в основе @retry(times=5), @cache(maxsize=100) и фреймворковых декораторов вроде @app.route("/users"). Как увидел трёхслойный паттерн — вся семья читается одинаково.

Стекинг декораторов

К функции можно применить несколько декораторов. Они стекаются снизу вверх: ближайший к def срабатывает первым:

main.py
Output
Click Run to see the output here.

Сначала оборачивает add_exclaim, прибавляя !. Потом сверху — shout, повышающий регистр. Вывод — HI, ROSA!.

Порядок важен. Если стек перевернуть, восклицательный знак добавится после повышения регистра — визуально здесь то же самое, но представь декоратор, форматирующий JSON: выполнить его до или после декоратора, логирующего вход, даст совсем разные результаты.

Встроенные декораторы, которые встретишь

У Python и его стандартной библиотеки есть декораторы, с которыми ты встретишься в реальном коде:

main.py
Output
Click Run to see the output here.
  • @property превращает метод в вычисляемый атрибут.
  • @staticmethod помечает метод, не использующий self или cls.
  • @classmethod получает класс как cls вместо экземпляра — отлично для альтернативных конструкторов.
  • @functools.lru_cache мемоизирует результаты, и повторные вызовы с теми же аргументами попадают в кэш.

Фреймворковые декораторы (@app.route, @pytest.fixture, @dataclass) работают по той же механике. Ничего особенного — просто функции, оборачивающие функции.

Когда писать, а когда не писать

Пиши декоратор, когда хочешь применить одно и то же поведение к многим функциям — тайминг, логирование, повторы, проверки доступа. Смысл в том, чтобы держать это поведение вне тела функции.

Пропусти декоратор, когда:

  • Поведение принадлежит одной конкретной функции. Положи его в функцию.
  • Нужен только для тестирования. Фикстура или параметр понятнее.
  • Тянет настакать их четыре-пять. В этот момент управление потоком прячется в цепочке декораторов — читателю придётся разматывать каждый слой, чтобы увидеть, что реально выполняется. Простой хелпер читается лучше.

Декораторы — острый инструмент. Используется хорошо — код сухой, намерение очевидное. Плохо — код скрывает, что делает. Склоняйся к «очевидному», когда решаешь, тянуться ли.

Дальше: аннотации типов

Декораторы — частое место, где встречаешь type hints в дикой природе: функции wrapper часто аннотируют свои сигнатуры. Type hints — маленькая фича, которая быстро окупается, и она на следующей странице.

Часто задаваемые вопросы

Что такое декоратор в Python?

Декоратор — это функция, которая принимает другую функцию и возвращает новую, обычно обёрнутую дополнительным поведением. Применяется через @decorator_name строкой выше def. Синтаксис @ — это сокращение для func = decorator_name(func).

Для чего используются декораторы?

Чтобы добавить поведение вокруг функции, не трогая её тело — логирование, тайминг, кэш, проверки авторизации, валидация входа, повторы. Фреймворки активно ими пользуются: @app.route(...) во Flask, @pytest.fixture в pytest, встроенные @property и @staticmethod.

Можно ли написать свой декоратор?

Да. Декоратор — это просто функция, принимающая функцию и возвращающая функцию. Большинство собственных декораторов оборачивают исходный вызов в маленькую внутреннюю функцию, которая что-то делает до, после или вокруг него. На внутреннюю функцию вешай functools.wraps, чтобы сохранить имя и docstring оригинала.

Учитесь программировать с Coddy

НАЧАТЬ