Декораторы одна из наиболее спорных и интересных тем в Python-сообществе. Ей посвящены десятки статей на хабре, но многие так и не могут понять до конца — зачем нужны декораторы?
Теория
Понятие работы декораторов в питоне начинается с одной базовой концепцией этого языка программирования — в Python все функции являются объектами. Это значит, что любая функция может быть привязана к переменной или быть возвращена другой функцией и стать аргументом. Всё это хорошо демонстрируют примеры:
[adace-ad id=»3482″]
#1 Функция = объект [code]
def function (whisky=»Lawsons Spiced»):
return whisky.capitalize() + «!»
print (function())
# выведет: ‘Lawsons spiced!’
# Так как функция — это объект, мы привязываем её к переменной, например, drinks
drinks = function
# Заметьте, что мы не используем скобок: мы НЕ вызываем функцию «functions»,
# мы связываем её с переменной «drinks». Это означает, что теперь мы
# можем вызывать «functions» через «drinks»:
print (drinks())
# выведет: ‘Lawsons spiced!’
# Более того, это значит, что мы можем удалить «shout», и функция всё ещё
# будет доступна через переменную «scream»
del function
try:
print (function)
except NameError:
print («Нет такой функции больше»)
# выведет: «Нет такой функции больше»
print (drinks())
# выведет: ‘Lawsons spiced!’
Результат:
#2 Определение функции внутри другой функции [code]
def alcohol():
# Внутри определения функции «алкоголь» мы можем определить другую…
def areyousure(word=»БУДУ!»):
return word.upper() + «…»
# … и сразу же её использовать!
print (areyousure())
# Теперь, КАЖДЫЙ РАЗ при вызове «Alcohol», внутри неё определяется а затем
# и вызывается функция «areyousure».
alcohol()
# выведет: «БУДУ!»
# Но вне функции «Alcohol» НЕ существует никакой функции «areyousure»:
try:
print (areyousure)
except NameError:
print («Нет такой функции»)
# выведет : «name ‘whisper’ is not defined»
Результат
#3 Функция возвращает другую функцию [code]
def getTalk(type=»shout»):
# Мы определяем функции прямо здесь
def shout(word=»да»):
return word.capitalize()+»!»
def whisper(word=»да») :
return word.lower()+»…»;
# Затем возвращаем необходимую
if type == «shout»:
# Заметьте, что мы НЕ используем «()», нам нужно не вызвать функцию,
# а вернуть объект функции
return shout
else:
return whisper
# Как использовать это непонятное нечто?
# Возьмём функцию и свяжем её с переменной
talk = getTalk()
# Как мы можем видеть, «talk» теперь — объект «function»:
print talk
# выведет: <function shout at 0xb7ea817c>
# Который можно вызывать, как и функцию, определённую «обычным образом»:
print talk()
# Если нам захочется — можно вызвать её напрямую из возвращаемого значения:
print getTalk(«whisper»)()
# выведет: да…
Результат
#4 Передача функции, как параметр другой функции [code]
def function (whisky=»Lawsons Spiced»):
return whisky.capitalize() + «!»
def function2(func):
print («Я буду пить только»)
print (func())
function2(function)
# выведет:
# Я делаю что-то ещё, перед тем как вызвать функцию, которую ты мне передал
# Lawsons spiced!
Результат
Практика
Последним примером мы напрямую подобрались к самой сути декоратора — «обёртки» для другой функции. С его помощью мы будем форматировать и рефакторить наш код
#5 Декоратор в действии [code]
# Декоратор — это функция, ожидающая ДРУГУЮ функцию в качестве параметра
def Alcohol(Whisky):
# Внутри себя декоратор определяет функцию-«обёртку».
# Она будет обёрнута вокруг декорируемой,
# получая возможность исполнять произвольный код до и после неё.
def drinks_whisky():
# Поместим здесь код, который мы хотим запускать ДО вызова
# оригинальной функции
print («Тут я еще трезвый»)
# ВЫЗОВЕМ саму декорируемую функцию
Whisky()
# А здесь поместим код, который мы хотим запускать ПОСЛЕ вызова
# оригинальной функции
print («Ох, епт, я в мясо»)
# На данный момент функция «Whisky» НЕ ВЫЗЫВАЛАСЬ НИ РАЗУ
# Теперь, вернём функцию-обёртку, которая содержит в себе
# декорируемую функцию, и код, который необходимо выполнить до и после.
# Всё просто!
return drinks_whisky()
# Представим теперь, что у нас есть функция, которую мы не планируем больше трогать.
def party():
print («Бухаю вискарь!УУхууу!»)
party()
# выведет: Бухаю вискарь!УУхууу!
# Однако, чтобы изменить её поведение, мы можем декорировать её, то есть
# Просто передать декоратору, который обернет исходную функцию в любой код,
# который нам потребуется, и вернёт новую, готовую к использованию функцию:
party = Alcohol(party)
# Теперь поведение вывода party изменится, т.к. она задекорирована
print (party)
# выведет:
# Тут я еще трезвый
# Бухаю вискарь!УУхууу!
# Ох, епт, я в мясо
# Если записать проще, в виде декоратора, то код будет выглядеть вот так:
@Alcohol
def party():
print («Бухаю вискарь!УУхууу!»)
print(party)
# выведет:
# Тут я еще трезвый
# Бухаю вискарь!УУхууу!
# Ох, епт, я в мясо
Результат:
В питоне так же есть и встроенные декораторы: @classmethod, @staticmethod, @property. А еще можно вызывать один декоратор за другим
#6 Использование нескольких декораторов [code]
def bread(func):
def wrapper():
print («</——\>»)
func()
print («<\______/>»)
return wrapper
def ingredients(func):
def wrapper():
print («#помидоры#»)
func()
print («~салат~»)
return wrapper
def sandwich(food=»—ветчина—«):
print (food)
sandwich()
# выведет: —ветчина—
sandwich = bread(ingredients(sandwich))
sandwich()
# выведет:
# </——\>
# #помидоры#
# —ветчина—
# ~салат~
# <\______/>
# ——————————- А теперь сделаем это через декораторы
# ——— Важна последовательность вызова декораторов
# — То есть внутри bread будет ingredients, внутри которого будет sandwich
@bread
@ingredients
def sandwich(food=»—ветчина—«):
print (food)
sandwich()
# выведет:
# </——\>
# #помидоры#
# —ветчина—
# ~салат~
# <\______/>
Результат:
