Вероятно, почти каждый разработчик на Python сталкивался с декораторами, видя конструкцию с со знаком @:
@app.route('/') def index(): return "Hello, World!"
Разберемся, что такое декоратор, и как он работает. Этот вопрос часто спрашивают на собеседованиях.
Декоратор – это функция, которая принимает как аргумент другую функцию*. Цель декоратора – расширить функциональность переданной ему функции без непосредственного изменения кода самой функции. Вот и все!
* Примечание: декорировать можно и класс, но об этом расскажу потом!
В Python функция – тоже объект, и ее можно передавать как аргумент, возвращать из другой функции, ей также можно назначать атрибуты.

Символ собачка (@) – всего лишь синтаксический сахар:
@decorator def foo(): ... # эквивалентно: def foo(): ... foo = decorator(foo)
Теперь, зная, что декоратор всего лишь функция – попробуем разобраться, что она может делать и как должна выглядеть. Декоратор может возвращать, что угодно, но по смыслу вернуть надо тоже функцию, чтобы заменить ей оригинальную функцию. Тривиальный декоратор возвращает свой аргумент, не делая ничего:
def decorator(f): return f
А можно вернуть и вообще другую функцию. Например, определенную внутри декоратора (да, внутри функций можно определять другие функции):
def decorator(f): def inner(): print('inner') return inner
В зависимости от назначения декоратора во внутренней функции мы можем вызывать сколько угодно раз исходную функцию, обрамлять вызов любым кодом и т. п. Например, декоратор, который печатает сообщения о начале и конце работы функции:
def decorator(f): def inner(): print('begin') f() print('end') return inner @decorator def foo(): print('foo') foo() # begin foo # foo # end foo
Здесь используется «замыкание», которое сохраняет переменную f в особой области видимости (enclosing), что дает возможность обращаться к f из inner после выхода из функции decorator.
Часто неизвестно, какие аргументы принимает f. Поэтому аргументы обычно обобщают, называя их *args (все позиционные аргументы, как список), **kwargs (все именованные аргументы, как словарь). Эти две штуки охватывают все возможные аргументы. Так же у функции может быть возвращаемое значение, которое неплохо также вернуть из inner. Улучшим наш декоратор, добавив прозрачную передачу любых (заранее неизвестных) аргументов и возвращение результата декорированной функции:
def decorator(f): def inner(*args, **kwargs): print('begin') result = f(*args, **kwargs) print('end') return result return inner @decorator def foo(x, y): print(f'summing {x} and {y}...') return x + y print(foo(5, y=10)) # begin # summing 5 and 10... # end # 15
@wraps
Помимо самого кода функции и ее аргументов, у нее также есть и другие свойства, например ее настоящее имя или «docstring» (это строчка с описанием функции в начале ее тела, которая хранится в атрибуте __doc__
и выводится при вызове help). Декоратор из прошлого примера потеряет документацию и имя функции (она станет зваться inner):
@decorator def foo(x, y): """Doc string here""" return x + y help(foo) # Help on function inner in module __main__: # inner(*args, **kwargs)
Для того, чтобы предотвратить потерю атрибутов декорированной функции в модуле functools есть декоратор wraps. Да, еще один декоратор, которым мы декорируем inner в нашем декораторе.

Вот теперь название и документация поступят в обертку из оригинальной функции:
from functools import wraps def decorator(f): @wraps(f) def inner(*args, **kwargs): print('begin') result = f(*args, **kwargs) print('end') return result return inner @decorator def foo(x, y): """Doc string here""" return x + y help(foo) # Help on function foo in module __main__: # foo(x, y) # Doc string here
Композиция декораторов
Можно применить несколько декораторов к одной функции. Вообще говоря, результат зависит от порядка следования декораторов: тот, что ближе к определению функции воздействует на нее раньше того, что дальше. Пример декораторов для пары HTML тэгов (для простоты я опустил формальности передачи аргументов и атрибутов из предыдущей части). foo и bar декорированы в разном порядке:
def bold(f): def inner(): return '<b>' + f() + '</b>' return inner def italic(f): def inner(): return '<i>' + f() + '</i>' return inner @bold @italic def foo(): return 'foo text' @italic @bold def bar(): return 'bar text' print(foo()) # <b><i>foo text</i></b> print(bar()) # <i><b>bar text</b></i>
И результат разный, потому что:
foo = italic(bold(foo)) bar = bold(italic(bar))
Пока хватит. В следующих частях:
- Параметры для декораторов
- Декоратор как класс
- Декорирование методов класса
- Примеры декораторов свои и из стандартных модулей
- Декорирование классов
🧙 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈