Декораторы Python от начала до конца

в 20:03, , рубрики: decorators, python, tutorial, переводы

Добрый день! Это мой первый перевод на хабре. Я программирую ради удовольствия и эта статья (на самом деле это ответ с stackoverflow) показалась мне достойной внимания. Во-первых — она огромна. Во-вторых, она прекрасна. Она помогла лично мне и я надеюсь, что кому-нибудь (например, таким же новичкам как и я) она также окажет содействие и утолит любопытство.
Все, что находится в таких "[..]" скобках — это мои комментарии. Все остальное — это мой перевод. Отчасти адаптированный, отчасти нет. Надеюсь, что он не очень плох и если всеже плох, рассчитываю на ваше понимание.
Поехали!

Python: Функции как объекты

Что бы понять что такое декораторы, для начала вы должны понять, что функции в python — это объекты. Данное понимание очень важно. Давайте разберем это на простом примере:

def shout(word="yes"):
    return word.capitalize()+"!"

print shout()
# результат: 'Yes!'

# Так как фунция - объект, вы можете присвоить её переменной, как любой другой объект.

scream = shout

# Отметьте, что мы не используем скобки: мы не вызываем функцию, мы
# помещаем функцию "shout" в переменную "scream". 
# это значит, что вы можете вызвать функцию "shout" из переменной "scream":

print scream()
# результат: 'Yes!'

# Более того, это значит, что вы можете удалить старое наименование 'shout', и
# функция при этом будет все еще доступна из переменной 'scream'

del shout
try:
    print shout()
except NameError, e:
    print e
    #результат: "name 'shout' is not defined"

print scream()
# результат: 'Yes!'


Хорошо, держите эту информацию в уме, мы скоро вернемся к ней. Другое интересное свойство функций в Python, это то, что они могут быть определены… внутри другой функции!


def talk():

    # Вы можете определить на лету функцию внутри функции "talk" ...
    def whisper(word="yes"):
        return word.lower()+"..."

    # ... и тотчас же использовать её!

    print whisper()

# Вы можете вызывать функцию "talk", которая определяет функцию "whisper" КАЖДЫЙ РАЗ, когда вы вызываете её("talk"), а затем вызывает
# функцию "whisper" внутри функции "talk". 
talk()
# результат: 
# "yes..."

# Но стоит заметить, что функция "whisper" не существует вне функции "talk":

try:
    print whisper()
except NameError, e:
    print e
    #результат : "name 'whisper' is not defined"*

Справка по функциям

Хорошо, вы все еще здесь? Теперь начнется веселая часть. Вы уже видели, что функции это объекты и поэтому они:
-Могут быть присвоены переменной;
-Могут быть определены внутри других функций.

В итоге, это означает, что функции могут возвращать другие функции :-) [возможно тут двойное значение 'return': 1) как оператор: 'могут вызывать оператор return для функций', так и действие 'to return'. Смысл не меняется абсолютно никак, мне просто показалось это забавным :-) прим. переводчика]
Давайте посмотрим:


def getTalk(type="shout"):

    # Мы на лету определяем функции
    def shout(word="yes"):
        return word.capitalize()+"!"

    def whisper(word="yes") :
        return word.lower()+"...";

    # А затем возвращаем одну из них
    if type == "shout":
        # Обратите внимание, что мы не используем "()", потому что мы не вызываем функцию,
        # мы возвращаем объект функции
        return shout  
    else:
        return whisper

# С чем и как это едят? [оригинальная фраза 'How do you use this strange beast?' дословно переводится как 'Как тебе использовать этого странного зверя?']

# Возьмем переменную и присвоим ей функцию
talk = getTalk()      

# Вы можете видеть, что функция "talk" здесь - это объект функции:
print talk
#результат : <function shout at 0xb7ea817c>

# Объект возвращаемый функцией:
print talk()
#результат : Yes!

# И конечно вы можете вызывать функцию напрямую, если вы чувствуете себя немного дико [оригинальная фраза 'feel wild']:
print getTalk("whisper")()
#результат : yes...

Но подождите, это еще не все! Если вы можете вернуть функцию, значит вы можете использовать ее как параметр:


def doSomethingBefore(func): 
    print "Я делаю что-то до того, как вызову переданную мне функцию"
    print func()

doSomethingBefore(scream)
#результат: 
#Я делаю что-то до того, как вызову переданную мне функцию
#Yes!

Отлично! Теперь вы знаете все что нужно, для того, чтобы понять декораторы. Декораторы это обертки, что означает,
что они позволяют исполнять код до и после вызова функции, которую они оборачивают без необходимости изменять саму функцию.

Декораторы своими руками

Как написать декоратор самостоятельно:


# Декоратор - это функция, которая ожидает другую функцию ввиде параметра
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)

Декораторы это просто Python вариант шаблона проектирования decorator. Вообще, для просты разработки в 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()
#результат:
#</'''''''''>
# #помидорки#
# --ветчинка--
# ~салатик~
#<_________/>

Тоже самое, используя синтаксис декораторов в Python:


@bread
@ingredients
def sandwich(food="--ветчинка--"):
    print food

sandwich()
#результат:
#</'''''''''>
# #помидорки#
# --ветчинка--
# ~салатик~
#<_________/>

Порядок в котором вы расставляете декораторы ИМЕЕТ значение:


@ingredients
@bread
def strange_sandwich(food="--ветчинка--"):
    print food

strange_sandwich()
#результат:
# #помидорки#
#</'''''''''>
# --ветчинка--
#<_________/>
# ~салатик~

[так как это перевод ответа с stackoverflow, тут располагался ответ на вопрос, он ничего нового не привносит и выпадает из контекста, поэтому я решил его убрать]

Теперь, вы можете просто уйти счастливыми или еще немного поднапрячь мозги и узнать как использовать декораторы на более продвинутом уровне!

Передача аргументов в функцию-декоратор


# Это не черная магия, вам просто нужно, чтобы обертка передала аргумент:

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, так это то, что методы и функции на самом деле одно и тоже, с одним отличием,
что метод ожидает первый параметр, который ссылается на конкретный объект (self).
Это значит, что вы можете сделать декоратор для метода тем же путем что и для функции, проcто держите в голове, что необходимо передать 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, **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 крутой язык, здесь нет аргументов"

function_with_no_argument()
#результат
#У меня есть аргументы?:
#()
#{}
#Python крутой язык, здесь нет аргументов

@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, вы просто говорите Python вызвать функцию определенную переменной «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 "Я декорируемая функция"
#результат:
#Я делаю декораторы! Я исполняюсь только один раз: когда создаю декоратор.
#Как создатель декораторов, я возвращаю декоратор!
#Я декоратор! Я исполняюсь только когда декорирую функцию.
#Так как я декоратор, я возвращаю оборачиваемую функцию.

#В итоге: 
decorated_function()    
#результат:
#Я обертка вокруг декорируемой функции. Я вызываюсь, когда вызывают декорируемую функцию.
#Так как я обертка, я возвращаю РЕЗУЛЬТАТ декорируемой функции.
#Я декорируемая функция.

Эй, вы это видели? Мы использовали вызов функции с "@" синтаксисом :-)

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


def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):

    print "Я делаю декораторы! И я принял аргументы:", decorator_arg1, decorator_arg2

    def my_decorator(func):
        # Возможность передавать аргументы в декораторе это подарок замыкания.
        # Если вы чувствуете себя не комфортно с замыканием, считайте, что все нормально,
        # или прочитайте: http://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python
        print "Я декоратор. Худо-бедно, вы передали мне аргументы:", decorator_arg1, decorator_arg2

        # Не перепутайте аргументы декоратора и аргументы функции!
        def wrapped(function_arg1, function_arg2) :
            print ("Я обертка вокруг декорируемой функции.n"
                  "Я могу получить доступ ко всем переменнымn"
                  "t- Из декоратора: {0} {1}n"
                  "t- Из вызова функции: {2} {3}n"
                  "Затем я передам иx декорируемой функции"
                  .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("Раджеш", "Говард")
#результат:
#Я делаю декораторы! И я принял аргументы: Леонард Шелдон
#Я декоратор. Худо-бедно, вы передали мне аргументы: Леонард Шелдон
#Я обертка вокруг декорируемой функции.
#Я могу получить доступ ко всем переменным 
#   - Из декоратора: Леонард Шелдон
#   - Из вызова функции: Раджеш Говард 
#Затем я передам иx декорируемой функции
#Я декорируемая функция и знаю только о своих аргументах: Раджеш Говард

Вот он, декоратор с аргументами. Аргументы могут быть назначены переменными:


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, "Говард")
#результат:
#Я делаю декораторы! И я принял аргументы: Леонард Пенни
#Я декоратор. Худо-бедно, вы передали мне аргументы: Леонард Пенни
#Я обертка вокруг декорируемой функции.
#Я могу получить доступ ко всем переменным 
#   - Из декоратора: Леонард Пенни
#   - Из вызова функции: Лесли Говард 
#Затем я передам иx декорируемой функции
#Я декорируемая функция и знаю только о своих аргументах: Лесли Говард

Как вы видите, вы можете передать аргументы декоратору как любой другой функции используя этот трюк. Вы так же можете использовать *args, **kwargs, если захотите. Но помните, декораторы вызываются только один раз. Тогда, когда Python импортирует скрипт. Вы не можете динамически назначать аргументы после этого. Когда вы выполняете «import x», функция уже декорирована, так что вы ничего не сможете изменить.

Немного практики: декоратор, для декорирования декораторов.

И как бонус, я дам вам снипет для создания любого декоратора, принимающего любую переменную. В конце концов, для того чтобы принимать аргументы, мы создали декоратор использующий функцию. Мы обернули декоратор. Все что мы видели в последнее время, просто обернутая функция? Ааа, декораторы! Давайте повеселимся и напишем декоратор для декоратора:


def decorator_with_args(decorator_to_enhance):
    """ 
    Предполагается, что эта функция будет использоваться как декоратор.
    Она должна декорировать другую функцию, которой также суждено использоваться как декоратору.
    Заварите себе чашечку кофе.
    Этот снипет позволит любому декоратору принимать произвольное число аргументов,
    избавляя вас от необходимости каждый раз напрягать мозги, вспоминая как это делается.
    """

    # Мы используем тот же трюк, что и был, чтобы передать аргументы
    def decorator_maker(*args, **kwargs):

        # Мы создаем на лету декоратор, который принимает только функцию
        # но сохраняет аргументы из генератора [декораторов. Замыкание вобщем]
        def decorator_wrapper(func):

            # Мы возвращаем результат исходного декоратора, который, после всего, 
            # ОБЫЧНАЯ ФУНКЦИЯ (которая возвращает функцию).
            # Единственная трудность: декоратор должен иметь конкретный указатель ['specific signature'], или он не будет работать:
            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 который копирует имя, модуль и строку документации любой «обернутой» функции в ее обертку. Забавный факт, functools.wraps — декоратор :-)


# Для дебага stacktrace [не нашел адекватного перевода] печатает вам __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

Как декораторы могут быть полезны?

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


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 = wrapper.count + 1
        res = func(*args, **kwargs)
        print "{0} был(а) использован(а): {1} раз".format(func.__name__, wrapper.count)
        return res
    wrapper.count = 0
    return wrapper

@counter
@benchmark
@logging
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
#wrapper был использован: 1 раз 
#алисон умен к рыс асырк - нёму но ,сил А
#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
#wrapper has been used: 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

И, конечно замечательно, что вы можете использовать декораторы для чего угодно и когда угодно без переписывания. DRY в действии:


@counter
@benchmark
@logging
def get_random_futurama_quote():
    import httplib
    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()


#результат:
#get_random_futurama_quote () {}
#wrapper 0.02
#wrapper был использован: 1 раз 
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#wrapper был использован: 2 раза 
#Curse you, merciful Poseidon!

# [английский текст выше - это цитаты с сайта к которому мы подключаемся
# через httplib, я не стал их переводить]

Сам по себе Python предоставляет несколько декораторов: property, staticmethod, etc. Django использует для управления кешированием и просмотру доступов. Twisted подделывает подстановку вызовов асинхронных функций. Это действительно больше поле деятельности.

Автор: Pruntoff

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js