Декораторы в Python
В Python декоратор — это шаблон проектирования, который позволяет изменять функциональность функции, оборачивая её в другую функцию.
Внешняя функция называется декоратором, она принимает исходную функцию в качестве аргумента и возвращает её изменённую версию.
Предварительные условия для изучения декораторов
Прежде чем изучать декораторы, нужно понять несколько важных концепций, связанных с функциями Python. Также помните, что в Python всё является объектом, включая функции.
Вложенная функция
Мы можем поместить одну функцию внутрь другой, это называется вложенной функцией. Например,
def outer(x):
def inner(y):
return x + y
return inner
add_five = outer(5)
result = add_five(6)
print(result) # выводит 11
# Вывод: 11
Здесь мы создали функцию inner() внутри функции outer().
Передача функции как аргумента
В Python мы можем передавать функцию в качестве аргумента другой функции. Например,
def add(x, y):
return x + y
def calculate(func, x, y):
return func(x, y)
result = calculate(add, 4, 6)
print(result) # выводит 10
Вывод
10
В приведённом примере функция calculate() принимает функцию в качестве своего аргумента. При вызове calculate() мы передаём функцию add() как аргумент.
В функции calculate() аргументы: func, x, y становятся add, 4 и 6 соответственно.
И поэтому func(x, y) становится add(4, 6), что возвращает 10.
Возврат функции как значения
В Python мы также можем возвращать функцию в качестве возвращаемого значения. Например,
def greeting(name):
def hello():
return "Hello, " + name + "!"
return hello
greet = greeting("Atlantis")
print(greet()) # выводит "Hello, Atlantis!"
# Вывод: Hello, Atlantis!
В приведённом примере оператор return hello возвращает внутреннюю функцию hello(). Эта функция теперь присваивается переменной greet.
Поэтому, когда мы вызываем greet() как функцию, получаем результат.
Декораторы в Python
Как упоминалось ранее, декоратор Python — это функция, которая принимает функцию и возвращает её, добавляя некоторую функциональность.
На самом деле любой объект, реализующий специальный метод __call__(), называется вызываемым. Так что в самом базовом смысле декоратор — это вызываемый объект, возвращающий вызываемый объект.
В сущности, декоратор принимает функцию, добавляет некоторую функциональность и возвращает её.
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
def ordinary():
print("I am ordinary")
# Вывод: I am ordinary
Здесь мы создали две функции:
ordinary(), которая выводит"I am ordinary"make_pretty(), которая принимает функцию в качестве аргумента, имеет вложенную функциюinner()и возвращает внутреннюю функцию.
Мы вызываем функцию ordinary() обычным способом, поэтому получаем вывод "I am ordinary". Теперь вызовем её через функцию-декоратор.
def make_pretty(func):
# определяем внутреннюю функцию
def inner():
# добавляем дополнительное поведение к декорируемой функции
print("I got decorated")
# вызываем оригинальную функцию
func()
# возвращаем внутреннюю функцию
return inner
# определяем обычную функцию
def ordinary():
print("I am ordinary")
# декорируем функцию ordinary
decorated_func = make_pretty(ordinary)
# вызываем декорированную функцию
decorated_func()
Вывод
I got decorated
I am ordinary
В показанном выше примере make_pretty() — это декоратор. Обратите внимание на код:
decorated_func = make_pretty(ordinary)
Мы передаём функцию
ordinary()как аргумент вmake_pretty().Функция
make_pretty()возвращает внутреннюю функцию, которая теперь присвоена переменной decorated_func.
decorated_func()
Здесь мы фактически вызываем функцию inner(), где выводим текст.
Символ @ с декоратором
Вместо присвоения вызова функции переменной Python предоставляет более элегантный способ достичь этой функциональности с помощью символа @. Например,
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
@make_pretty
def ordinary():
print("I am ordinary")
ordinary()
Вывод
I got decorated
I am ordinary
Здесь функция ordinary() декорируется декоратором make_pretty() с использованием синтаксиса @make_pretty, что эквивалентно вызову ordinary = make_pretty(ordinary).
Декорирование функций с параметрами
Приведённый выше декоратор был простым и работал только с функциями без параметров. Что, если у нас есть функции, принимающие параметры, например:
def divide(a, b):
return a/b
Эта функция имеет два параметра, a и b. Мы знаем, что она выдаст ошибку, если передать b равным 0.
Теперь создадим декоратор для проверки этого случая, вызывающего ошибку.
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)
divide(2,5)
divide(2,0)
Вывод
I am going to divide 2 and 5
0.4
I am going to divide 2 and 0
Whoops! cannot divide
Здесь, когда мы вызываем функцию divide() с аргументами (2,5), вместо неё вызывается функция inner(), определённая в декораторе smart_divide().
Эта функция inner() вызывает исходную функцию divide() с аргументами 2 и 5 и возвращает результат, который равен 0.4.
Аналогично, когда мы вызываем функцию divide() с аргументами (2,0), функция inner() проверяет, что b равно 0, и выводит сообщение об ошибке перед возвратом None.
Цепочки декораторов в Python
В Python можно объединять несколько декораторов в цепочки.
Чтобы объединить декораторы в цепочку в Python, мы можем применить несколько декораторов к одной функции, размещая их один за другим, при этом самый внутренний декоратор применяется первым.
def star(func):
def inner(*args, **kwargs):
print("*" * 15)
func(*args, **kwargs)
print("*" * 15)
return inner
def percent(func):
def inner(*args, **kwargs):
print("%" * 15)
func(*args, **kwargs)
print("%" * 15)
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
***************
%%%%%%%%%%%%%%%