image_pdf

Тема декораторов довольно часто, однако, обсуждается и эта статья (выросшая из одного вопроса на stackoverflow) наиболее полно, по-моему мнению, раскрывает тему и, что немаловажно, является «пошаговым руководством» для использованию декораторов, позволяющим новичку овладеть этой техникой сразу на достойном уровне.

Итак, что же такое «декоратор»?

Впереди достаточно длинная статья, так что, если кто-то спешит — вот пример того, как работают декораторы:

def makebold(fn):
    def wrapped():
        return "" + fn() + ""
    return wrapped
 
def makeitalic(fn):
    def wrapped():
        return "" + fn() + ""
    return wrapped
 
@makebold
@makeitalic
def hello():
    return "hello habr"
 
print hello() ## выведет hello habr


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

Функции в 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()
# выведет: 'Да!'

Ссылки на функции

Ну что, вы всё ещё здесь?:)

Теперь мы знаем, что функции являются полноправными объектами, а значит:

  • могут быть связаны с переменной;
  • могут быть определены одна внутри другой.

Что ж, а это значит, что одна функция может вернуть другую функцию!
Давайте посмотрим:

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()
#выведет:
# Я - код, который отработает до вызова функции
# Оставь меня в покое
# А я - код, срабатывающий после

Да, всё действительно так просто! decorator — просто синтаксический сахар для конструкций вида:

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()
#выведет:
# #помидоры#
# 
# --ветчина--
# <\______/>
# ~салат~

На этом моменте Вы можете счастливо уйти, с осознанием того, что вы поняли, что такое декораторы и с чем их едят.

Для тех же, кто хочет помучать ещё немного свой мозг, вторая часть статьи, посвящённая продвинутому использованию декораторов.

Декораторы, которые мы до этого рассматривали не имели одного очень важного функционала — передачи аргументов декорируемой функции.

Что ж, исправим это недоразумение!

Передача («проброс») аргументов в декорируемую функцию

Никакой чёрной магии, всё, что нам необходимо — собственно, передать аргументы дальше!

def a_decorator_passing_arguments(function_to_decorate):
    def a_wrapper_accepting_arguments(arg1, arg2): # аргументы прибывают отсюда
        print "Смотри, что я получил:", arg1, arg2
        function_to_decorate(arg1, arg2)
    return a_wrapper_accepting_arguments
 
# Теперь, когда мы вызываем функцию, которую возвращает декоратор,
# мы вызываем её "обёртку", передаём ей аргументы и уже в свою очередь
# она передаёт их декорируемой функции
 
@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
    print "Меня зовут", first_name, last_name
 
print_full_name("Питер", "Венкман")
# выведет:
# Смотри, что я получил: Питер Венкман
# Меня зовут Питер Венкман
# *

* — Прим. переводчика: Питер Венкман — имя одного из Охотников за приведениями, главного героя одноименного культового фильма.

Декорирование методов

Один из важных фактов, которые следует понимать, заключается в том, что функции и методы в Python’e — это практически одно и то же, за исключением того, что методы всегда ожидают первым параметром ссылку на сам объект (self). Это значит, что мы можем создавать декораторы для методов так же, как и для функций, просто не забывая про self.

def method_friendly_decorator(method_to_decorate):
    def wrapper(self, lie):
        lie = lie - 3 # действительно, дружелюбно - снизим возраст ещё сильней :-)
        return method_to_decorate(self, lie)
    return wrapper
 
 
class Lucy(object):
 
    def __init__(self):
        self.age = 32
 
    @method_friendly_decorator
    def sayYourAge(self, lie):
        print "Мне %s, а ты бы сколько дал?" % (self.age + lie)
 
l = Lucy()
l.sayYourAge(-3)
# выведет: Мне 26, а ты бы сколько дал?

Конечно, если мы создаём максимально общий декоратор и хотим, чтобы его можно было применить к любой функции или методу, то стоит воспользоваться тем, что *args распаковывает список args, а **kwargs распаковывает словарь kwargs:

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    # Данная "обёртка" принимает любые аргументы
    def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
        print "Передали ли мне что-нибудь?:"
        print args
        print kwargs
        # Теперь мы распакуем *args и **kwargs
        # Если вы не слишком хорошо знакомы с распаковкой, можете прочесть следующую статью:
        # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
        function_to_decorate(*args, **kwargs)
    return a_wrapper_accepting_arbitrary_arguments
 
@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print "Python is cool, no argument here." # оставлено без перевода, хорошая игра слов:)
 
function_with_no_argument()
# выведет:
# Передали ли мне что-нибудь?:
# ()
# {}
# Python is cool, no argument here.
 
@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print a, b, c
 
function_with_arguments(1,2,3)
# выведет:
# Передали ли мне что-нибудь?:
# (1, 2, 3)
# {}
# 1 2 3
 
@a_decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, platypus="Почему нет?"):
    print "Любят ли %s, %s и %s утконосов? %s" %\
    (a, b, c, platypus)
 
function_with_named_arguments("Билл", "Линус", "Стив", platypus="Определенно!")
# выведет:
# Передали ли мне что-нибудь?:
# ('Билл', 'Линус', 'Стив')
# {'platypus': 'Определенно!'}
# Любят ли Билл, Линус и Стив утконосов? Определенно!
 
class Mary(object):
 
    def __init__(self):
        self.age = 31
 
    @a_decorator_passing_arbitrary_arguments
    def sayYourAge(self, lie=-3): # Теперь мы можем указать значение по умолчанию
        print "Мне %s, а ты бы сколько дал?" % (self.age + lie)
 
m = Mary()
m.sayYourAge()
# выведет:
# Передали ли мне что-нибудь?:
# (<__main__ .Mary object at 0xb7d303ac>,)
# {}
# Мне 28, а ты бы сколько дал?

Вызов декоратора с различными аргументами

Отлично, с этим разобрались. Что вы теперь скажете о том, чтобы попробовать вызывать декораторы с различными аргументами?

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

Так что, перед тем, как показать вам решение, я бы хотел освежить в памяти то, что мы уже знаем:

# Декораторы - это просто функции
def my_decorator(func):
    print "Я обычная функция"
    def wrapper():
        print "Я - функция, возвращаемая декоратором"
        func()
    return wrapper
 
# Так что, мы можем вызывать её, не используя "@"-синтаксис:
 
def lazy_function():
    print "zzzzzzzz"
 
decorated_function = my_decorator(lazy_function)
# выведет: Я обычная функция
 
# Данный код выводит "Я обычная функция", потому что это ровно то, что мы сделали:
# вызвали функцию. Ничего сверхъестественного
 
@my_decorator
def lazy_function():
    print "zzzzzzzz"
 
# выведет: Я обычная функция

Как мы видим, это два аналогичных действия. Когда мы пишем @my_decorator — мы просто говорим интерпретатору «вызвать функцию, под названием my_decorator». Это важный момент, потому что данное название может как привести нас напрямую к декоратору… так и нет!
Давайте сделаем нечто страшное!:)

def decorator_maker():
 
    print "Я создаю декораторы! Я буду вызван только раз: "+\
          "когда ты попросишь меня создать тебе декоратор."
 
    def my_decorator(func):
 
        print "Я - декоратор! Я буду вызван только раз: в момент декорирования функции."
 
        def wrapped():
            print ("Я - обёртка вокруг декорируемой функции. "
                  "Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию. "
                  "Я возвращаю результат работы декорируемой функции.")
            return func()
 
        print "Я возвращаю обёрнутую функцию."
 
        return wrapped
 
    print "Я возвращаю декоратор."
    return my_decorator
 
# Давайте теперь создадим декоратор. Это всего лишь ещё один вызов функции
new_decorator = decorator_maker()
# выведет:
# Я создаю декораторы! Я буду вызван только раз: когда ты попросишь меня создать тебе декоратор. 
# Я возвращаю декоратор.
 
# Теперь декорируем функцию
 
def decorated_function():
    print "Я - декорируемая функция."
 
decorated_function = new_decorator(decorated_function)
# выведет:
# Я - декоратор! Я буду вызван только раз: в момент декорирования функции.
# Я возвращаю обёрнутую функцию.
 
# Теперь наконец вызовем функцию:
decorated_function()
# выведет:
# Я - обёртка вокруг декорируемой функции. Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию.
# Я возвращаю результат работы декорируемой функции.
# Я - декорируемая функция.

Длинно? Длинно. Перепишем данный код без использования промежуточных переменных:

def decorated_function():
    print "Я - декорируемая функция."
decorated_function = decorator_maker()(decorated_function)
# выведет:
# Я создаю декораторы! Я буду вызван только раз: когда ты попросишь меня создать тебе декоратор. 
# Я возвращаю декоратор.
# Я - декоратор! Я буду вызван только раз: в момент декорирования функции.
# Я возвращаю обёрнутую функцию.
 
# Наконец:
decorated_function()
# выведет:
# Я - обёртка вокруг декорируемой функции. Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию.
# Я возвращаю результат работы декорируемой функции.
# Я - декорируемая функция.

А теперь ещё раз, ещё короче:

@decorator_maker()
def decorated_function():
    print "I am the decorated function."
# выведет:
# Я создаю декораторы! Я буду вызван только раз: когда ты попросишь меня создать тебе декоратор. 
# Я возвращаю декоратор.
# Я - декоратор! Я буду вызван только раз: в момент декорирования функции.
# Я возвращаю обёрнутую функцию.
 
# И снова:
decorated_function()
# выведет:
# Я - обёртка вокруг декорируемой функции. Я буду вызвана каждый раз когда ты вызываешь декорируемую функцию.
# Я возвращаю результат работы декорируемой функции.
# Я - декорируемая функция.

Вы заметили, что мы вызвали функцию, после знака «@»?:)

Вернёмся, наконец, к аргументам декораторов, ведь если мы используем функцию, чтобы создавать декораторы «на лету», мы можем передавать ей любые аргументы, верно?

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
 
    print "Я создаю декораторы! И я получил следующие аргументы:", decorator_arg1, decorator_arg2
 
    def my_decorator(func):
        print "Я - декоратор. И ты всё же смог передать мне эти аргументы:", decorator_arg1, decorator_arg2
 
        # Не перепутайте аргументы декораторов с аргументами функций!
        def wrapped(function_arg1, function_arg2) :
            print ("Я - обёртка вокруг декорируемой функции.\n"
                  "И я имею доступ ко всем аргументам: \n"
                  "\t- и декоратора: {0} {1}\n"
                  "\t- и функции: {2} {3}\n"
                  "Теперь я могу передать нужные аргументы дальше"
                  .format(decorator_arg1, decorator_arg2,
                          function_arg1, function_arg2))
            return func(function_arg1, function_arg2)
 
        return wrapped
 
    return my_decorator
 
@decorator_maker_with_arguments("Леонард", "Шелдон")
def decorated_function_with_arguments(function_arg1, function_arg2):
    print ("Я - декорируемая функция и я знаю только о своих аргументах: {0}"
           " {1}".format(function_arg1, function_arg2))
 
decorated_function_with_arguments("Раджеш", "Говард")
# выведет:
# Я создаю декораторы! И я получил следующие аргументы: Леонард Шелдон
# Я - декоратор. И ты всё же смог передать мне эти аргументы: Леонард Шелдон
# Я - обёртка вокруг декорируемой функции.
# И я имею доступ ко всем аргументам: 
#   - и декоратора: Леонард Шелдон
#   - и функции: Раджеш Говард
# Теперь я могу передать нужные аргументы дальше
# Я - декорируемая функция и я знаю только о своих аргументах: Раджеш Говард

* — Прим. переводчика: в данном примере автор упоминает имена главных героев популярного сериала «Теория Большого взрыва».
Вот он, искомый декоратор, которому можно передавать произвольные аргументы.

Безусловно, аргументами могут быть любые переменные:

c1 = "Пенни"
c2 = "Лесли"
 
@decorator_maker_with_arguments("Леонард", c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
    print ("Я - декорируемая функция и я знаю только о своих аргументах: {0}"
           " {1}".format(function_arg1, function_arg2))
 
decorated_function_with_arguments(c2, "Говард")
# выведет:
# Я создаю декораторы! И я получил следующие аргументы: Леонард Пенни
# Я - декоратор. И ты всё же смог передать мне эти аргументы: Леонард Пенни
# Я - обёртка вокруг декорируемой функции.
# И я имею доступ ко всем аргументам: 
#   - и декоратора: Леонард Пенни
#   - и функции: Лесли Говард
# Теперь я могу передать нужные аргументы дальше
# Я - декорируемая функция и я знаю только о своих аргументах: Лесли Говард

Таким образом, мы можем передавать декоратору любые аргументы, как обычной функции. Мы можем использовать и распаковку через *args и **kwargs в случае необходимости.

Но необходимо всегда держать в голове, что декоратор вызывается ровно один раз. Ровно в момент, когда Python импортирует Ваш скрипт. После этого мы уже не можем никак изменить аргументы, с которыми.

Когда мы пишем «import x» все функции из x декорируются сразу же, и мы уже не сможем ничего изменить.

Немного практики: напишем декоратор декорирующий декоратор

Если вы дочитали до этого момента и ещё в строю — вот вам бонус от меня.

Это небольшая хитрость позволит вам превратить любой обычный декоратор в декоратор, принимающий аргументы.

Изначально, чтобы получить декоратор, принимающий аргументы, мы создали его с помощью другой функции.

Мы обернули наш декоратор.

Есть ли у нас что-нибудь, чем можно обернуть функцию?
Точно, декораторы!

Давайте же немного развлечёмся и напишем декоратор для декораторов:

def decorator_with_args(decorator_to_enhance):
    """
    Эта функция задумывается КАК декоратор и ДЛЯ декораторов.
    Она должна декорировать другую функцию, которая должна быть декоратором.
    Лучше выпейте чашку кофе.
    Она даёт возможность любому декоратору принимать произвольные аргументы,
    избавляя Вас от головной боли о том, как же это делается, каждый раз, когда этот функционал необходим.
    """
 
    # Мы используем тот же трюк, который мы использовали для передачи аргументов:
    def decorator_maker(*args, **kwargs):
 
        # создадим на лету декоратор, который принимает как аргумент только 
        # функцию, но сохраняет все аргументы, переданные своему "создателю"
        def decorator_wrapper(func):
 
            # Мы возвращаем то, что вернёт нам изначальный декоратор, который, в свою очередь
            # ПРОСТО ФУНКЦИЯ (возвращающая функцию).
            # Единственная ловушка в том, что этот декоратор должен быть именно такого
            # decorator(func, *args, **kwargs)
            # вида, иначе ничего не сработает
            return decorator_to_enhance(func, *args, **kwargs)
 
        return decorator_wrapper
 
    return decorator_maker

Это может быть использовано так:

# Мы создаём функцию, которую будем использовать как декоратор и декорируем её :-)
# Не стоит забывать, что она должна иметь вид "decorator(func, *args, **kwargs)"
@decorator_with_args
def decorated_decorator(func, *args, **kwargs):
    def wrapper(function_arg1, function_arg2):
        print "Мне тут передали...:", args, kwargs
        return func(function_arg1, function_arg2)
    return wrapper
 
# Теперь декорируем любую нужную функцию нашим новеньким, ещё блестящим декоратором:
 
@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
    print "Привет", function_arg1, function_arg2
 
decorated_function("Вселенная и", "всё прочее")
# выведет:
# Мне тут передали...: (42, 404, 1024) {}
# Привет Вселенная и всё прочее
 
# Уфффффф!

Думаю, я знаю, что Вы сейчас чувствуете.

Последний раз Вы испытывали это ощущение, слушая, как вам говорят: «Чтобы понять рекурсию необходимо для начала понять рекурсию».

Но ведь теперь Вы рады, что разобрались с этим?;)

Рекомендации для работы с декораторами

  • Декораторы были введены в Python 2.4, так что узнавайте, на чём будет выполняться Ваш код.
  • Декораторы несколько замедляют вызов функции, не забывайте об этом.
  • Вы не можете «раздекорировать» функцию. Безусловно, существуют трюки, позволяющие создать декоратор, который можно отсоединить от функции, но это плохая практика. Правильней будет запомнить, что если функция декорирована — это не отменить.
  • Декораторы оборачивают функции, что может затруднить отладку.

Последняя проблема частично решена в Python 2.5, добавлением в стандартную библиотеку модуля functools включающего в себя functools.wraps, который копирует всю информацию об оборачиваемой функции (её имя, из какого она райомодуля, её docstrings и т.п.) в функцию-обёртку.

Забавным фактом является то, что functools.wraps — сам по себе декоратор.

# Во время отладки, в трассировочную информацию выводится __name__ функции.
def foo():
    print "foo"
 
print foo.__name__
# выведет: foo
 
# Однако, декораторы мешают нормальному ходу дел:
def bar(func):
    def wrapper():
        print "bar"
        return func()
    return wrapper
 
@bar
def foo():
    print "foo"
 
print foo.__name__
# выведет: wrapper
 
# "functools" может нам с этим помочь
 
import functools
 
def bar(func):
    # Объявляем "wrapper" оборачивающим "func"
    # и запускаем магию:
    @functools.wraps(func)
    def wrapper():
        print "bar"
        return func()
    return wrapper
 
@bar
def foo():
    print "foo"
 
print foo.__name__
# выведет: foo

Как можно использовать декораторы?

И в заключение, я бы хотел ответить на вопрос, который я часто слышу: зачем же нужны декораторы? Как их можно использовать?
Декораторы могут быть использованы для расширения возможностей функций из сторонних библиотек (код которых мы не можем изменять), или для упрощения отладки (мы не хотим изменять код, который ещё не устоялся).

Так же полезно использовать декораторы для расширения различных функций одним и тем же кодом, без повторного его переписывания каждый раз, например:

def benchmark(func):
    """
    Декоратор, выводящий время, которое заняло
    выполнение декорируемой функции.
    """
    import time
    def wrapper(*args, **kwargs):
        t = time.clock()
        res = func(*args, **kwargs)
        print func.__name__, time.clock() - t
        return res
    return wrapper
 
 
def logging(func):
    """
    Декоратор, логирующий работу кода.
    (хорошо, он просто выводит вызовы, но тут могло быть и логирование!)
    """
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        print func.__name__, args, kwargs
        return res
    return wrapper
 
 
def counter(func):
    """
    Декоратор, считающий и выводящий количество вызовов
    декорируемой функции.
    """
    def wrapper(*args, **kwargs):
        wrapper.count += 1
        res = func(*args, **kwargs)
        print "{0} была вызвана: {1}x".format(func.__name__, wrapper.count)
        return res
    wrapper.count = 0
    return wrapper
 
 
@benchmark
@logging
@counter
def reverse_string(string):
    return str(reversed(string))
 
print reverse_string("А роза упала на лапу Азора")
print reverse_string("A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!")
 
# выведет:
# reverse_string ('А роза упала на лапу Азора',) {}
# wrapper 0.0
# reverse_string была вызвана: 1x
# арозА упал ан алапу азор А
# reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {}
# wrapper 0.0
# reverse_string была вызвана: 2x
# !amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A

Таким образом, декораторы можно применить к любой функции, расширив её функционал и не переписывая ни строчки кода!

import httplib
 
@benchmark
@logging
@counter
def get_random_futurama_quote():
    conn = httplib.HTTPConnection("slashdot.org:80")
    conn.request("HEAD", "/index.html")
    for key, value in conn.getresponse().getheaders():
        if key.startswith("x-b") or key.startswith("x-f"):
            return value
    return "Эх, нет... не могу!"
 
print get_random_futurama_quote()
print get_random_futurama_quote()
 
#outputs:
#get_random_futurama_quote () {}
#wrapper 0.02
#get_random_futurama_quote была вызвана: 1x
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#get_random_futurama_quote была вызвана: 2x
#Curse you, merciful Poseidon!

В Python включены такие декораторы как property, staticmethod и т.д.

В Django декораторы используются для управления кешированием, контроля за правами доступа и определения обработчиков адресов. В Twisted — для создания поддельных асинхронных inline-вызовов.

Декораторы открывают широчайший простор для экспериментов! И надеюсь, что данная статья поможет Вам в его освоении!
Спасибо за внимание!

Источники вдохновения:

Опубликовано Вадим В. Костерин

Ст.преп. кафедры ИТЭ. Автор более 130 научных и учебно-методических работ. Лауреат ВДНХ (серебряная медаль).

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *