Декоратор — это функция, которая оборачивает функцию
Фраза звучит абстрактно, но механика простая. Декоратор принимает функцию и возвращает функцию. Возвращённая обычно вызывает исходную, накручивая сверху дополнительное поведение.
Самый короткий пример:
shout — декоратор. Он принимает функцию (greet), строит новую функцию (wrapper), которая вызывает оригинал и повышает регистр результата, и возвращает её. Переприсваивание greet = shout(greet) подменяет оригинал на обёрнутую версию.
Этот паттерн переприсваивания настолько частый, что Python дал ему отдельный синтаксис.
@ — сахар для переприсваивания
@name строкой выше def эквивалентно name = name(...) сразу после определения функции:
@shout читается как «примени декоратор shout к этой функции». Python запускает greet = shout(greet) сразу после def — та же механика, меньше набора.
Как увидишь @name — мысленно заменяй на function = name(function). Это всё, что означает синтаксис.
Обработка аргументов
У большинства функций есть аргументы. Пригодный декоратор пробрасывает их дальше. Идиома — *args, **kwargs, способ Python принять любые аргументы: обёртке не должно быть важно, что ждёт обёрнутая функция:
*args забирает все позиционные аргументы. **kwargs — все keyword-аргументы. Обёртка пробрасывает всё в обёрнутую функцию без изменений и делает дополнительную работу, ради которой декоратор и нужен, — здесь поднимает регистр результата.
Такую форму принимает большинство реальных декораторов.
Более полезный пример: тайминг
Выводим, сколько занимает функция:
Паттерн — «запустить что-то до вызова, запустить что-то после» — то, чем в итоге занимается большинство декораторов. Логирование, проверки авторизации, повторы и валидация — одной формы.
Сохраняем оригинал: functools.wraps
Декорирование функции её заменяет, и обёрнутая функция теряет исходные атрибуты __name__ и __doc__:
greet.__name__ теперь "wrapper", docstring исчез. Это ломает help(), трейсбэки и любые инструменты, инспектирующие функцию.
Ремонт — одна строка: @functools.wraps(func) на внутренней функции копирует метаданные.
Всегда ставь @wraps(func) на внутреннюю функцию. Это ничего не стоит и избавляет от удивлённых отладочных сессий позже.
Декораторы с аргументами
Иногда самому декоратору нужна настройка — «повтори функцию до 3 раз», «логируй на уровне DEBUG». Это ещё один слой вложенности: внешняя функция принимает аргументы и возвращает декоратор.
Три слоя звучит как много. Читай изнутри наружу:
repeat(times=3)— вызов функции. Возвращаетdecorator.decorator— собственно декоратор, принимает функцию и возвращает обёрнутую.wrapper— обёрнутая функция, работающая в момент вызова.
Эта форма лежит в основе @retry(times=5), @cache(maxsize=100) и фреймворковых декораторов вроде @app.route("/users"). Как увидел трёхслойный паттерн — вся семья читается одинаково.
Стекинг декораторов
К функции можно применить несколько декораторов. Они стекаются снизу вверх: ближайший к def срабатывает первым:
Сначала оборачивает add_exclaim, прибавляя !. Потом сверху — shout, повышающий регистр. Вывод — HI, ROSA!.
Порядок важен. Если стек перевернуть, восклицательный знак добавится после повышения регистра — визуально здесь то же самое, но представь декоратор, форматирующий JSON: выполнить его до или после декоратора, логирующего вход, даст совсем разные результаты.
Встроенные декораторы, которые встретишь
У Python и его стандартной библиотеки есть декораторы, с которыми ты встретишься в реальном коде:
@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 оригинала.