Понимаем декораторы в Python’e, шаг за шагом. Шаг 1
Те же из вас, кто готов потратить немного времени, приглашаются прочесть длиииинный пост.
Функции в Python’e являются объектами
Для того, чтобы понять, как работают декораторы, в первую очередь следует осознать, что в Python’е функции — это тоже объекты.
Давайте посмотрим, что из этого следует:
def shout(word="да"): return word.capitalize() + "!" print shout() # выведет: 'Да!' # Так как функция - это объект, вы можете связать её с переменнной, # как и любой другой объект scream = shout # Заметьте, что мы не используем скобок: мы НЕ вызываем функцию "shout", # мы связываем её с переменной "scream". Это означает, что теперь мы # можем вызывать "shout" через "scream": print scream() # выведет: 'Да!' # Более того, это значит, что мы можем удалить "shout", и функция всё ещё # будет доступна через переменную "scream" del shout try: print shout() except NameError, e: print e #выведет: "name 'shout' is not defined" print scream() # выведет: 'Да!'
Запомним этот факт, скоро мы к нему вернёмся, но кроме того, стоит понимать, что функция в Python’e может быть определена… внутри другой функции!
def talk(): # Внутри определения функции "talk" мы можем определить другую. def whisper(word="да"): return word.lower() + ". " # . и сразу же её использовать! print whisper() # Теперь, КАЖДЫЙ РАЗ при вызове "talk", внутри неё определяется а затем # и вызывается функция "whisper". talk() # выведет: "да. " # Но вне функции "talk" НЕ существует никакой функции "whisper": try: print whisper() except NameError, e: print e #выведет : "name 'whisper' is not defined"
Ссылки на функции
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 # выведет: # Который можно вызывать, как и функцию, определённую "обычным образом": print talk() # Если нам захочется - можно вызвать её напрямую из возвращаемого значения: print getTalk("whisper")() # выведет: да.
Подождите, раз мы можем возвращать функцию, значит, мы можем и передавать её другой функции, как параметр:
def doSomethingBefore(func): print "Я делаю что-то ещё, перед тем как вызвать функцию, которую ты мне передал" print func() doSomethingBefore(scream) #выведет: # Я делаю что-то ещё, перед тем как вызвать функцию, которую ты мне передал # Да!
Ну что, теперь у нас есть все необходимые знания для того, чтобы понять, как работают декораторы.
Как вы могли догадаться, декораторы — это, по сути, просто своеобразные «обёртки», которые дают нам возможность делать что-либо до и после того, что сделает декорируемая функция, не изменяя её.
Создадим свой декоратор «вручную»
# Декоратор - это функция, ожидающая ДРУГУЮ функцию в качестве параметра def my_shiny_new_decorator(a_function_to_decorate): # Внутри себя декоратор определяет функцию-"обёртку". # Она будет (что бы вы думали. ) обёрнута вокруг декорируемой, # получая возможность исполнять произвольный код до и после неё. def the_wrapper_around_the_original_function(): # Поместим здесь код, который мы хотим запускать ДО вызова # оригинальной функции print "Я - код, который отработает до вызова функции" # ВЫЗОВЕМ саму декорируемую функцию a_function_to_decorate() # А здесь поместим код, который мы хотим запускать ПОСЛЕ вызова # оригинальной функции print "А я - код, срабатывающий после" # На данный момент функция "a_function_to_decorate" НЕ ВЫЗЫВАЛАСЬ НИ РАЗУ # Теперь, вернём функцию-обёртку, которая содержит в себе # декорируемую функцию, и код, который необходимо выполнить до и после. # Всё просто! return the_wrapper_around_the_original_function # Представим теперь, что у нас есть функция, которую мы не планируем больше трогать. def a_stand_alone_function(): print "Я – простая одинокая функция, ты ведь не посмеешь меня изменять. " a_stand_alone_function() # выведет: Я – простая одинокая функция, ты ведь не посмеешь меня изменять. # Однако, чтобы изменить её поведение, мы можем декорировать её, то есть # Просто передать декоратору, который обернет исходную функцию в любой код, # который нам потребуется, и вернёт новую, готовую к использованию функцию: a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function_decorated() #выведет: # Я - код, который отработает до вызова функции # Я простая одинокая функция, ты ведь не посмеешь меня изменять. # А я - код, срабатывающий после
Наверное, теперь мы бы хотели, чтобы каждый раз, во время вызова a_stand_alone_function, вместо неё вызывалась a_stand_alone_function_decorated. Нет ничего проще, просто перезапишем a_stand_alone_function функцией, которую нам вернул my_shiny_new_decorator:
a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function() #выведет: # Я - код, который отработает до вызова функции # Я простая одинокая функция, ты ведь не посмеешь меня изменять. # А я - код, срабатывающий после
Вы ведь уже догадались, что это ровно тоже самое, что делают @декораторы.:)
Разрушаем ореол таинственности вокруг декораторов
@my_shiny_new_decorator def another_stand_alone_function(): print "Оставь меня в покое" another_stand_alone_function() #выведет: # Я - код, который отработает до вызова функции # Оставь меня в покое # А я - код, срабатывающий после
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
Декораторы — это просто pythonic-реализация паттерна проектирования «Декоратор». В Python включены некоторые классические паттерны проектирования, такие как рассматриваемые в этой статье декораторы, или привычные любому пайтонисту итераторы.
Конечно, можно вкладывать декораторы друг в друга, например так:
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 def sandwich(food="--ветчина--"): print food sandwich() #выведет: #------\> # #помидоры# # --ветчина-- # ~салат~ # <\______/>
Следует помнить о том, что порядок декорирования ВАЖЕН:
@ingredients @bread def sandwich(food="--ветчина--"): print food sandwich() #выведет: # #помидоры# #------\> # --ветчина-- # <\______/># ~салат~
На этом моменте Вы можете счастливо уйти, с осознанием того, что вы поняли, что такое декораторы и с чем их едят.
Для тех же, кто хочет помучать ещё немного свой мозг, завтра будет допереведена вторая часть статьи, посвящённая продвинутому использованию декораторов.