Декоратор - это функция, которая оборачивает функцию
Фраза звучит абстрактно, но механика простая. Декоратор принимает функцию и возвращает функцию. Возвращённая обычно вызывает исходную, накручивая сверху дополнительное поведение.
Самый короткий пример:
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 оригинала.