Декораторы в питоне
Как перестать бояться декораторов и начать писать собственные.
Реклама
Вы наверняка сталкивались с декораторами в Python, но знаете ли вы, как они работают? Можете без подсказки написать свой декоратор? А если вы только начинаете изучать Python и ещё не знакомы с ними, то не за горами тот момент, когда вы с ними так или иначе встретитесь, так почему бы не ускорить встречу?
Небольшое теоретическое вступление.
Прежде чем мы поговорим о декораторах, рассмотрим один небольшой, но важный нюанс, касающийся функций. Функции, как и всё в Python, являются объектами. И, как и любой другой объект, мы можем передавать функцию в качестве аргумента в другую функцию. Рассмотрим это на следующем примере.
def func_one():
print("Как дела?”)
def func_two(func):
func()
func_two(func_one) # Как дела?
Вот, что у нас здесь происходит:
1️⃣ Сначала мы создаем функцию func_one
, которая выводит на печать строку “Как дела?”.
2️⃣Затем создаем вторую функцию func_two
. Она принимает в качестве аргумента некоторую функцию func
, которую в дальнейшем вызывает. Обратим внимание на то, что для передачи функции в качестве аргумента мы пишем только название функции - func
, но не ставим скобки, так как в этот момент не вызываем ее.
3️⃣ Последним действием вызовем функцию func_two
, передав в нее func_one
. Как мы видим, func_one
успешно вызывается внутри func_two
, о чем и возвещает, интересуясь, как у нас дела.
Функции-обертки.
Давайте немного усложним рассматриваемую конструкцию:
def deco(func):
def wrapper():
print("Привет!")
func()
print("Пока!")
return wrapper
def func_one():
print("Как дела?")
deco(func_one)
1️⃣ Создаем функцию deco
, в которую передадим в качестве аргумента некоторую функцию func
.
2️⃣ Внутри функции deco
создаем еще одну функцию wrapper
. В ней сначала выводим на печать приветствие (“Привет!”), затем вызываем функцию func
, которую получаем из внешней функции deco
при помощи замыкания (обращения к объекту из внешней области видимости). Далее выводим на печать прощание (“Пока!”).
3️⃣ Функция deco
возвращает функцию wrapper
.
4️⃣ Создаем уже известную нам функцию func_one
, вопрошающую как дела.
5️⃣ Вызываем функцию deco
.
Что же мы получим? Ничего. Всё дело в том, что функция deco
возвращает функцию-обёртку wrapper
, но не вызывает её. Чтобы заставить всю конструкцию работать, нам надо сделать кое-что странное: поставить после вызова функции deco
дополнительную пару скобок.
deco(func_one)()
# Привет!
# Как дела?
# Пока!
Вот теперь у нас всё заблестело и заискрилось: функция wrapper
, которую вернула нам вызванная функция deco
, вызвалась дополнительной парой скобок и подарила нам короткий, но вежливый монолог: “Привет! Как дела? Пока!”.
А где же декораторы?
Терпение, спокойствие, сейчас они появятся.
Как нам сделать так, чтобы вызов функции func_one
всегда сопровождался оборачиванием ее в функцию wrapper
? Другими словами, как нам отдекорировать func_one
, чтобы до вопроса о делах с нами здоровались, а после – прощались?
Решение как будто напрашивается: нам надо перезаписать в переменную func_one
результат вызова функции deco
с переданной в нее в качестве аргумента изначальной функцией func_one
:
func_one = deco(func_one)
Еще раз: у нас была функция func_one
. Мы обернули ее дополнительным кодом при помощи функции deco
и в переменную func_one
записали уже результат этого оборачивания. Теперь когда бы мы ни вызвали func_one
, мы всегда получим усовершенствованный (отдекорированный) результат ее работы:
func_one()
# Привет!
# Как дела?
# Пока!
А есть ли более удобный вариант записи для вызова функции декоратора? Есть - при помощи символа @
.
Интересный факт: символ @, по мнению разработчиков, похож на пирог, от чего такой способ вызова декоратора назвали "Pie Decorator Syntax"
Полностью наш код будет выглядеть следующим образом:
def deco(func):
def wrapper():
print("Привет!")
func()
print("Пока!")
return wrapper
@deco
def func_one():
print("Как дела?")
func_one()
# Привет!
# Как дела?
# Пока!
Результат работы этого кода точно такой же, просто синтаксис стал попроще и посимпатичнее. У большинства само понятие «декоратор» плотно ассоциируется с этим «пирожковым» оператором, но не самом деле декорирование - это не про синтаксис. Декорирование - это про принцип.
А что делать, если нам надо отдекорировать функцию, которая принимает какие-то аргументы?
В этой ситуации на помощь приходят звёздные братья *args
и **kwargs
. Многих начинающих питонистов эта парочка пугает, но на самом деле бояться здесь нечего. Это всего лишь синтаксис, который позволяет нам не уточнять, сколько аргументов мы хотим передать в функцию:
*args
кортежем передает в функцию любое количество позиционных аргументов (то есть тех, к которым внутри функции можно обратиться по позиции).**kwargs
словарём передает в функцию любое количество именованных аргументов (то есть тех, к которым внутри функции можно обратиться по имени, или, другими словами, по ключу). Вот простенький пример, из которого всё должно стать понятно:
def my_function(*args, **kwargs):
print(args[1])
print(kwargs[name])
my_function(1, 2, 3, name='John', age=25)
def my_function(*args, **kwargs):
print(args[1])
print(kwargs['name'])
my_function(1, 2, 3, name='Аристарх', age=25)
# 2
# Аристарх
Мы передали в функцию my_function
заранее неоговоренное количество позиционных (1, 2, 3)
и именованных (name="Аристарх", age=25)
аргументов. В самой функции по индексу 1 мы вызвали второй элемент кортежа позиционных аргументов (2) и по ключу "name" вызвали из словаря значение ("Аристарх"). Ничего сложного. Надо только следить за тем, чтобы в функции не вызывался аргумент с несуществующим индексом в кортеже или с несуществующим ключом в словаре. Иначе работа кода прервется исключением. Исключение, правда, можно и обработать, но об этом поговорим как-нибудь в другой раз.
Вернёмся к нашим декораторам.
Допустим, что строку в нашей функции func
мы не прописываем уже внутри, а передаем в функцию позиционным аргументом. Тогда уже знакомый нам код изменится следующим образом:
def deco(func):
def wrapper(*args, **kwargs):
print("Привет!")
func(*args, **kwargs)
print("Пока!")
return wrapper
@deco
def func_one(text):
print(text)
func_one("Как дела?")
# Привет!
# Как дела?
# Пока!
1️⃣ В функцию-обертку (wrapper
) мы записываем те самые *args
и **kwargs
2️⃣ В функцию func
внутри wrapper
мы также их передаём
3️⃣ В декорируемую функцию передаем аргумент text
В итоге всё работает ровно так, как мы и хотели, но функция и декоратор приобрели более универсальный вид.
Так что же такое декораторы?
Декоратор - это функция, которая позволяет нам "обернуть” кодом другую функцию, не изменяя при этом код самой оборачиваемой функции. Это даёт нам возможность выполнить какие-то дополнительные действия до и/или после выполнения самой оборачиваемой функции. Например, как в нашем примере выше, поздороваться перед тем, как сказать что-то, и попрощаться после этого. В принципе декораторы похожи на ритуалы, которые мы все выполняем в обычной жизни. Например, перед тем, как что-то съесть, мы моем руки. При этом неважно, из чего будет состоять трапеза: перед едой все хорошие мальчики и девочки обязательно вымоют руки - таков декоратор, обернувший функцию “поесть” в процессе воспитания в детстве.
Самым распространённым примером использования декоратора в коде является логгер. Он срабатывает при выполнении декорируемой функции и записывает в файл результат выполнения, а также сопутствующие данные, такие как название функции, входные аргументы и другие.
Вообще декораторами бывают не только функции. В декоратор можно превратить целый класс, но об этом поговорим в отдельной статье.
Все статьи