Декораторы Python: как использовать и зачем?

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

Декораторы в Python

У Python есть интересная функция, называемая декораторами, для добавления функциональности в существующий код.

Это также называется метапрограммированием, потому что часть программы пытается изменить другую часть программы во время компиляции.

Предпосылки для изучения декораторов

Чтобы понять, что такое декораторы, мы должны сначала знать несколько основных вещей в Python.

Мы должны быть довольны тем фактом, что все в Python (да, даже классы) являются объектами. Имена, которые мы определяем, являются просто идентификаторами, привязанными к этим объектам. Функции не исключение, они тоже объекты (с атрибутами). К одному и тому же функциональному объекту могут быть привязаны разные имена.

Вот пример.

 def first(msg): print(msg) first("Hello") second = first second("Hello")

Вывод

 Привет привет

Когда вы запускаете код, обе функции firstи secondдают одинаковый результат. Здесь имена firstи secondотносятся к одному и тому же объекту функции.

Теперь все становится еще более странным.

Функции можно передавать в качестве аргументов другой функции.

Если вы использовали такие функции, как 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() # Outputs "Hello" new()

Вывод

 Здравствуйте

Вот is_returned()вложенная функция, которая определяется и возвращается каждый раз, когда мы вызываем is_called().

Наконец, мы должны знать о замыканиях в 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 >>> # let's decorate this ordinary function >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary

В примере, показанном выше, make_pretty()это декоратор. На этапе присвоения:

 pretty = make_pretty(ordinary)

Функция ordinary()была оформлена, и возвращаемой функции было присвоено имя pretty.

Мы видим, что функция декоратора добавила некоторые новые функции к исходной функции. Это похоже на упаковку подарка. Декоратор действует как обертка. Характер декорированного объекта (настоящего подарка внутри) не меняется. Но сейчас это выглядит красиво (раз уж разукрашено).

Обычно мы украшаем функцию и переназначаем ее как,

 ordinary = 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("I am going to divide", a, "and", b) if b == 0: print("Whoops! cannot divide") return return func(a, b) return inner @smart_divide def divide(a, b): print(a/b)

Эта новая реализация вернется, Noneесли возникнет состояние ошибки.

 >>> divide(2,5) I am going to divide 2 and 5 0.4 >>> divide(2,0) I am going to divide 2 and 0 Whoops! cannot divide

Таким образом мы можем украсить функции, которые принимают параметры.

Внимательный наблюдатель заметит, что параметры вложенной inner()функции внутри декоратора такие же, как параметры функций, которые он украшает. Учитывая это, теперь мы можем создавать генеральные декораторы, работающие с любым количеством параметров.

In Python, this magic is done as function(*args, **kwargs). In this way, args will be the tuple of positional arguments and kwargs will be the dictionary of keyword arguments. An example of such a decorator will be:

 def works_for_all(func): def inner(*args, **kwargs): print("I can decorate any function") return func(*args, **kwargs) return inner

Chaining Decorators in Python

Multiple decorators can be chained in Python.

This is to say, a function can be decorated multiple times with different (or same) decorators. We simply place the decorators above the desired function.

 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")

Output

 ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************

The above syntax of,

 @star @percent def printer(msg): print(msg)

is equivalent to

 def printer(msg): print(msg) printer = star(percent(printer))

The order in which we chain decorators matter. If we had reversed the order as,

 @percent @star def printer(msg): print(msg)

The output would be:

 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ****************************** Hello ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Интересные статьи...