Декораторы принимают функцию, добавляют в нее некоторую функциональность и возвращают ее обратно. Из данного пособия вы узнаете, как создавать декораторы и зачем это нужно делать.
Декораторы в Python
Для придания новой функциональности уже существующему коду в Python есть очень интересный инструмент под названием декоратор (decorator).
Это также можно назвать метапрограммированием, так как отдельные части программы меняют ее другие части в процессе компиляции.
Необходимые условия для понимания данного материала
Чтобы понять что такое декораторы, вы должны знать несколько базовых вещей в Python.
В первую очередь вас не должно удивлять, что в Python все (и даже классы!) является объектами. Задаваемые нами имена — это просто идентификаторы, привязанные к конкретным объектам. Функции также не являются исключением, это такие же объекты со своими атрибутами. К одной и той же функции может быть привязано несколько совершенно разных имен.
Вот пример:
def first(msg): print(msg) first("Hello") second = first second("Hello") # Результат: Hello Hello
При работе данного кода обе функции, first
и second
, дают один и тот же результат. Это оттого, что имена first
и second
относятся к одному объекту.
Теперь сгустим немного краски.
Функции могут быть переданы в качестве аргументов в другие функции!
Если вы в Python пользовались функциями типа map
, filter
или reduce
, то уже об этом знаете.
Такие функции в Python называются функциями высшего порядка. Вот пример такой функции:
def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result
Мы вызываем такую функцию следующим образом:
>>> operate(inc,3) 4 >>> operate(dec,3) 2
Более того, функция может возвращать другую функцию.
def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() # Результат "Hello" new()
Результат:
Hello
Здесь is_returned()
— это вложенная функция, так как она определена внутри функции is_called()
. И она возвращается каждый раз, как только мы вызываем is_called()
.
И еще, мы должны знать, как работают замыкания в Python.
Возвращаемся обратно к декораторам
Функции и методы в Python являются вызываемыми объектами, поскольку могут быть вызваны.
Фактически, любой объект в Python, для которого реализован специальный метод __call__()
, является вызываемым. Таким образом, в наиболее общем смысле декоратор является вызываемым объектом и также возвращает вызываемый объект.
В общем и целом, декоратор принимает функцию, добавляет в нее некоторую функциональность и возвращает её.
def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary")
Когда вы запускаете данный код в интерпретаторе, происходит следующее:
>>> ordinary() I am ordinary >>> # Давайте декорируем функцию ordinary() >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary
В данном примере функция make_pretty()
является декоратором. Декорирование происходит вот на этом шаге:
pretty = make_pretty(ordinary)
Функция ordinary()
теперь задекорирована и возвращаемая функция носит имя pretty()
.
Мы видим, что декоратор добавил некоторую новую функциональность в первоначальную функцию. Это напоминает упаковку для подарка. Декоратор играет роль такой упаковки. Суть самого объекта в результате декорирования не меняется. Но теперь этот объект выглядит симпатичнее (что не удивительно, он ведь был декорирован).
Обычно мы декорируем и переопределяем функцию следующим образом:
pretty = make_pretty(ordinary)
Это довольно частая конструкция, и поэтому в Python есть синтаксис для ее упрощения.
Мы используем символ @
перед названием функции и помещаем его прямо над объявлением функции, которая должна быть задекорирована. Например:
@make_pretty def ordinary(): print("I am ordinary")
Это эквивалентно следующему коду:
def ordinary(): print("I am ordinary") ordinary = make_pretty(ordinary)
Такой вот синтаксический сахар для реализации декораторов.
Декорирование функций с параметрами
Разобранный нами декоратор был очень простым и мог работать только с функциями без параметров. А как быть, если у функции есть параметры? Например, вот такие:
def divide(a, b): return a/b
У этой функции есть два параметра, a
и b
и мы также знаем, что если b
будет равно 0
, возникнет ошибка.
>>> divide(2, 5) 0.4 >>> divide(2, 0) Traceback (most recent call last): ... ZeroDivisionError: division by zero
Теперь давайте сделаем декоратор, который будет проверять наличие нуля и предотвращать ошибку.
def smart_divide(func): def inner(a, b): print("Я собираюсь разделить", a, "на", b) if b == 0: print("Упс! Не могу выполнить деление") return return func(a, b) return inner @smart_divide def divide(a, b): print(a/b)
В новой реализации функция вернет None
, если возникнет ситуация с делением на 0
:
>>> divide(2,5) Я собираюсь разделить 2 на 5 0.4 >>> divide(2,0) Я собираюсь разделить 2 на 0 Упс! Не могу выполнить деление
Таким образом мы можем декорировать функции, имеющие параметры.
Внимательный читатель может заметить, что вложенная внутри декоратора функция inner()
имеет те же параметры, что и функция, которую мы декорируем. Приняв это во внимание, мы теперь можем построить декоратор для функции с произвольным количеством параметров.
В Python эта магия выглядит следующим образом: function(*args, **kwargs)
. Здесь *args
— это кортеж позиционных аргументов, а **kwargs
— словарь ключевых аргументов. Вот пример такого декоратора:
def works_for_all(func): def inner(*args, **kwargs): print("Я могу декорировать любую функцию") return func(*args, **kwargs) return inner
Создание цепочек декораторов
В Python может последовательно применяться сразу несколько декораторов.
Иными словами, функция может быть декорирована несколько раз разными (или одними и теми же!) декораторами. Мы просто помещаем эти декораторы поверх функции, которую хотим задекорировать.
def star(func): def inner(*args, **kwargs): print("*" * 30) func(*args, **kwargs) print("*" * 30) return inner def percent(func): def inner(*args, **kwargs): print("%" * 30) func(*args, **kwargs) print("%" * 30) return inner @star @percent def printer(msg): print(msg) printer("Hello")
Результат:
****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************
Данный синтаксис
@star @percent def printer(msg): print(msg)
эквивалентен следующему:
def printer(msg): print(msg) printer = star(percent(printer))
Порядок, в котором мы применяем декораторы, имеет значение. Например, изменив порядок следующим образом:
@percent @star def printer(msg): print(msg)
Мы получим вот такой результат:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ****************************** Hello ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%