Как скомпилировать декоратор

Содержание:

Введение в абстракции высокого уровня

По моему опыту, большей частью метаклассы применяются для изменения методов в классе после создания его экземпляра. Декораторы в настоящее время не позволяют изменять сам процесс создания экземпляра класса, но они могут корректировать методы, связанные с классом. Это не позволяет вам добавлять или удалять атрибуты методов или классов динамически, в момент создания экземпляра, но позволяет методам изменять свое поведение в зависимости от окружающей среды во время выполнения. Технически декоратор применяется при исполнении предложения , что для классов верхнего уровня скорее ближе к «времени компиляции», чем к «времени исполнения». Однако организовать определение декораторов во время исполнения так же просто, как создать фабрику классов. Например:

Листинг 8. Реально работающий, хотя и глубоко вложенный, декоратор
def arg_sayer(what):

    def what_sayer(meth):
        def new(self, *args, **kws):
            print what
            return meth(self, *args, **kws)
        return new
    return what_sayer

def FooMaker(word):
    class Foo(object):
        @arg_sayer(word)
        def say(self): pass
    return Foo()

foo1 = FooMaker('this')
foo2 = FooMaker('that')
print type(foo1),; foo1.say()  # prints: <class '__main__.Foo'> this
print type(foo2),; foo2.say()  # prints: <class '__main__.Foo'> that

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

  • Метод ведет себя по-разному для разных экземпляров. В примере все различие сводится к значению величины, которое можно было бы изменять и другим способом, но, вообще говоря, декоратор мог бы переписывать метод полностью, основываясь на информации, получаемой в момент исполнения.
  • Недекорированный метод в этом случае представляет собой простую «заглушку», а все поведение определяется декоратором. Однако в других случаях декоратор может объединять поведение недекорированного метода с некоторыми новыми способностями.
  • Как уже говорилось, модификация определяется строго во время исполнения, путем использования фабрики классов . Возможно, более типичным является применение декораторов к классам, определенным на верхнем уровне, которые зависят только от условий, доступных во время компиляции (зачастую этого достаточно).
  • Декоратор параметризирован. Или, скорее, сам по себе вообще не является реально декоратором. Скорее уж функция, возвращаемая, а именно , представляет собой функцию декоратора, которая использует оболочку для инкапсулирования данных. Параметризация декораторов является обычным делом, но она приводит к необходимости вложения необходимых функций на трехуровневую глубину.

Как отлаживать декораторы

В этот момент вы, наверное, видели, что мы используем декораторы для обертывания вокруг функции. Обертывание функции это по сути замыкание. Замыкание скрывает исходное имя функции, список ее параметров и строку документации docstring.

Например: если мы попытаемся получить метаданные для декоратора function_with_arguments, мы получим метаданные замыкания (то есть функции обертки). Давайте продемонстрируем это:

function_with_arguments.__name__  

Output

'my_wrapper'  

Это представляет большую проблему во время отладки. Тем не менее, Python предоставляет декоратор functools.wraps, который может помочь в решении этой проблемы. Он работает путем копирования потерянных метаданных в ваше замыкание .

Теперь давайте продемонстрируем, как это работает:

import functools

def lowercase(func):  
    @functools.wraps(func)
    def my_wrapper():
        return func().lower()
    return my_wrapper
@lowercase
def hello_function():  
    "Saying hello"
    return 'HELLO WORLD'

print(hello_function())  

Output

hello world  

Поскольку мы использовали functools.wraps в функции-обертке, мы можем проверить метаданные функции на «hello_function»:

hello_function.__name__  

Output

'hello_function'  
hello_function.__doc__  

Output

'Saying hello'  

Приведенный выше скрипт ясно показывает, что метаданные теперь ссылаются на функцию, а не на оболочку. Я рекомендую вам всегда использовать functools.wraps каждый раз, когда вы определяете декоратор. Это намного упростит отладку, в случае необходимости.

Заключение

Цель декораторов — динамически изменять функциональные возможности класса, метода или функции без непосредственного использования наследования или изменения исходного кода класса, метода или функции, которую нам нужно декорировать. В этой статье мы увидели, как создавать простые и универсальные декораторы и как передавать аргументы декораторам. Мы также увидели, как отлаживать декораторы во время разработки с помощью модуля functools.

Оригинал: Introduction to Python Decorators

Spread the love

Summing it up (TL;DR)

In this Python Decorators Tutorial, we explained all the beauty of decorators. You can now use them to make your code cleaner and better in general. For those of you in a hurry, here you have the key points.

  • Apply a decorator by adding this syntax just above the function you want to decorate:
  • The decorator may accept parameters if that’s the case just go with
  • You may apply multiple decorators, one per line. However, Python will apply them in bottom-up order.
  • To define a decorator, you need to write a function that accepts a function as a single parameter. Inside that function, create another function: this is the altered function, and your decorator should return just that. This internal function, the “wrapper” may call the original function and perform additional operations

What brought you to decorators? How are you planning to use them? Let me know what you think in the comments, and I’ll be glad to answer!

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

Функции и методы синтаксически похожи.

Разница в том, что функция вызывается только по имени.

func()

А вызов метода осуществляется через оператор “.” и вводится имя метода, где первым параметром является родитель.

object.func()

Таким образом, декораторы Python для методов создаются так же, как для функции.

Здесь мы создали декоратор def method_friendly_decorator(method_to_decorate):

def wrapper(self, lie):
lie = lie — 3 #
return method_to_decorate(self, lie)
return wrapper

F здесь создали класс с методами, которые позже будут модифицированы^

class Lucy(object):
def __init__(self):
self.age = 32

@method_friendly_decorator
def sayYourAge(self, lie):
print «Мне %s, а ты бы сколько дал?» % (self.age + lie)

Lucy.sayYourAge(-32)
#Мне 26, а ты бы сколько дал?

Здесь использован format(). Предназначается для форматирование строк, используется в таком виде:

print(“string {} {}”).format(1, 2)
#string 1 2​

В этом примере в аргументах format() указаны две цифры: 1, 2. Они замещают символы {} в таком порядке, в котором расположены. Форматирование доступно исключительно для строковых элементов. То есть, аргумент становится на место первых фигурных скобок {}, а второй — вторых, соответственно. В методе format предусмотрена возможность менять порядок вставки значений. Это делается через индексы.

Если:

print(“string {} {}”).format(1, 2)
#string 1 2

То:

print(“string {1} {0}”).format(1, 2)
#string 2 1

Допускается форматирование строк через ключевые имена в формате format(arg1 = value1, arg2 = value2).

print(“string {arg1} {arg2}”).format(arg1 = 1, arg2 = 2)
#string 1 2

Можно использовать смешанную систему — когда при двух аргументах только один из них имеет статическое значение. Для передачи значений указывается индекс и имя переменной.

print(“string {arg1} {1}”).format(arg1 = 1, 2)
#string 1 2

Using Functions as Decorators¶

The only constraint on the result of a decorator is that it be callable, so it
can properly replace the decorated function. In the above examples, I’ve
replaced the original function with an object of a class that has a
method. But a function object is also callable, so we can rewrite
the previous example using a function instead of a class, like this:

# PythonDecorators/entry_exit_function.py
def entry_exit(f):
    def new_f():
        print("Entering", f.__name__)
        f()
        print("Exited", f.__name__)
    return new_f

@entry_exit
def func1():
    print("inside func1()")

@entry_exit
def func2():
    print("inside func2()")

func1()
func2()
print(func1.__name__)

is defined within the body of , so it is created and
returned when is called. Note that is a closure,
because it captures the actual value of .

Once has been defined, it is returned from so that
the decorator mechanism can assign the result as the decorated function.

The output of the line is , because the
function has been substituted for the original function during
decoration. If this is a problem you can change the name of the decorator
function before you return it:

def entry_exit(f):
    def new_f():
        print("Entering", f.__name__)
        f()
        print("Exited", f.__name__)
    new_f.__name__ = f.__name__
    return new_f

Изменение модели вызова

Вероятнее всего, самое типичное применение декораторов, с которым вам придется сталкиваться – это оснащение функции или метода какими-либо дополнительными функциями вдобавок к их основному назначению. Например, на Web-сайте Python Cookbook (см. ссылку в разделе ) и в других подобных местах можно встретить декораторы, добавляющие такие возможности, как трассировка, ведение журнала, запоминание/кэширование, блокировка потоков, перенаправление вывода. Сходны с такими модификациями, хотя и немного в другом духе, модификации «до» и «после». Одно интересное применение «до/после»-декорирования – это проверка типов аргументов и возвращаемого значения функции. Предполагается, что подобный декоратор должен вызвать исключение или предпринять какие-нибудь корректирующие действия, если типы не те, которые ожидаются.

Продолжая мыслить в духе «до/после»-декораторов, я дошел до идеи «поэлементного» применения функций, типичного для языка программирования R и . В этих языках численные функции обычно действуют на каждый элемент в последовательности элементов, а также и на отдельное число.

Разумеется, функция , построители списков (list-comprehensions) и с недавнего времени построители генераторов (generator-comprehensions) позволяют осуществлять поэлементное применение. Но для этого приходится прибегать к мелким ухищрениям, чтобы сымитировать возможности языка R: последовательность, возвращаемая , всегда имеет тип «список», и вызов завершится ошибкой, если вы передадите ей отдельный элемент, а не последовательность. Например:

Листинг 11. Вызов map(), который завершится ошибкой
>>> from math import sqrt
>>> map(sqrt, (4, 16, 25))

>>> map(sqrt, 144)
TypeError: argument 2 to map() must support iteration

Нетрудно создать декоратор, который «улучшает» обычную численную функцию:

Листинг 12. Преобразование функции в поэлементную функцию
def elementwise(fn):
    def newfn(arg):
        if hasattr(arg,'__getitem__'):  # is a Sequence
            return type(arg)(map(fn, arg))
        else:
            return fn(arg)
    return newfn

@elementwise
def compute(x):
    return x**3 - 1

print compute(5)        # prints: 124
print compute()  # prints: 
print compute((1,2,3))  # prints: (0, 7, 26)

Нетрудно, конечно, просто написать функцию , в которую будут встроены разные типы возвращаемых значений; в конце концов, декоратор занимает всего несколько строк. Однако в качестве своего рода «реверанса» в сторону аспектно-ориентированного программирования этот пример позволяет нам разделить задачи разных уровней. Мы могли бы написать множество функций для численных вычислений и захотеть перевести каждую из них на поэлементную модель вызова, не задумываясь о тестировании типов аргументов и принудительной установки типа возвращаемого значения.

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

Большинство хороших декораторов, с которыми вам придется сталкиваться, в значительной степени использует эту парадигму объединения ортогональных задач (concerns). В традиционном объектно-ориентированном программировании, особенно на таких языках как Python, которые допускают многократное наследование, предпринимается попытка модуляризировать задачи в рамках иерархии наследования. Однако для того, чтобы просто брать одни методы от одного родителя, а другие методы — от других родителей, необходима концепция, в которой задачи разделены намного сильнее, чем это имеет место в аспектно-ориентированном понимании. Чтобы максимально эффективно использовать генераторы, необходимо смотреть на проблемы несколько иначе, чем при простом комбинировании методов: любой метод можно сделать работающим по-разному в зависимости от задач, которые находятся вне «сердцевины» самого метода.

Decorating Functions with Parameters

The above decorator was simple and it only worked with functions that did not have any parameters. What if we had functions that took in parameters like:

This function has two parameters, a and b. We know it will give an error if we pass in b as 0.

Now let’s make a decorator to check for this case that will cause the error.

This new implementation will return if the error condition arises.

In this manner, we can decorate functions that take parameters.

A keen observer will notice that parameters of the nested function inside the decorator is the same as the parameters of functions it decorates. Taking this into account, now we can make general decorators that work with any number of parameters.

In Python, this magic is done as . In this way, will be the tuple of positional arguments and will be the dictionary of keyword arguments. An example of such a decorator will be:

Decorator Location

The first syntax point is the location of the decorators. For the
following examples, we use the @syntax used in 2.4a2.

Decorators before the def statement are the first alternative, and the
syntax used in 2.4a2:

@classmethod
def foo(arg1,arg2):
    pass

@accepts(int,int)
@returns(float)
def bar(low,high):
    pass

There have been a number of objections raised to this location — the
primary one is that it’s the first real Python case where a line of code
has an effect on a following line. The syntax available in 2.4a3
requires one decorator per line (in a2, multiple decorators could be
specified on the same line), and the final decision for 2.4 final stayed
one decorator per line.

People also complained that the syntax quickly got unwieldy when
multiple decorators were used. The point was made, though, that the
chances of a large number of decorators being used on a single function
were small and thus this was not a large worry.

Some of the advantages of this form are that the decorators live outside
the method body — they are obviously executed at the time the function
is defined.

Another advantage is that a prefix to the function definition fits
the idea of knowing about a change to the semantics of the code before
the code itself, thus you know how to interpret the code’s semantics
properly without having to go back and change your initial perceptions
if the syntax did not come before the function definition.

Guido decided he preferred having the decorators on the line before
the ‘def’, because it was felt that a long argument list would mean that
the decorators would be ‘hidden’

The second form is the decorators between the def and the function name,
or the function name and the argument list:

def @classmethod foo(arg1,arg2):
    pass

def @accepts(int,int),@returns(float) bar(low,high):
    pass

def foo @classmethod (arg1,arg2):
    pass

def bar @accepts(int,int),@returns(float) (low,high):
    pass

There are a couple of objections to this form. The first is that it
breaks easily ‘greppability’ of the source — you can no longer search
for ‘def foo(‘ and find the definition of the function. The second,
more serious, objection is that in the case of multiple decorators, the
syntax would be extremely unwieldy.

The next form, which has had a number of strong proponents, is to have
the decorators between the argument list and the trailing in the
‘def’ line:

def foo(arg1,arg2) @classmethod:
    pass

def bar(low,high) @accepts(int,int),@returns(float):
    pass

Guido summarized the arguments against this form (many of which also
apply to the previous form) as:

  • it hides crucial information (e.g. that it is a static method)
    after the signature, where it is easily missed
  • it’s easy to miss the transition between a long argument list and a
    long decorator list
  • it’s cumbersome to cut and paste a decorator list for reuse, because
    it starts and ends in the middle of a line

The next form is that the decorator syntax goes inside the method body at
the start, in the same place that docstrings currently live:

def foo(arg1,arg2):
    @classmethod
    pass

def bar(low,high):
    @accepts(int,int)
    @returns(float)
    pass

The primary objection to this form is that it requires «peeking inside»
the method body to determine the decorators. In addition, even though
the code is inside the method body, it is not executed when the method
is run. Guido felt that docstrings were not a good counter-example, and
that it was quite possible that a ‘docstring’ decorator could help move
the docstring to outside the function body.

The final form is a new block that encloses the method’s code. For this
example, we’ll use a ‘decorate’ keyword, as it makes no sense with the
@syntax.

decorate:
    classmethod
    def foo(arg1,arg2):
        pass

decorate:
    accepts(int,int)
    returns(float)
    def bar(low,high):
        pass

Аргументы функции

В аргументах декораторов Python передается любой тип данных.

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

Обычный.
С помощью ключевых слов.
Задание статических значений.
Использование позиционных элементов.

Вам будет интересно:SQL CREATE DATABASE Statement

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

def bigger(a,b):
if a > b:
print a
else:
print b

Правильный вызов:

bigger(5,6)

Неправильный вызов:

bigger(3)
bigger(12,7,3)

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

def person(name, age):
print name, «is», age, «years old»
person(age=23, name=»John»)

Статические значения переменных создаются вместе с функцией через оператор присваивания так, как если бы инициализация происходила в теле.

def space(planet_name, center=»Star»):
print(planet_name, «is orbiting a», center)
space(«Mars»)

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

def func(*args):
return args
func(1, 2, 3, ‘abc’)
# (1, 2, 3, ‘abc’)
func(1)
#(1,)

Похожим способом передаются библиотеки значений с ключами — с помощью символа “**”.

Переменные, указанные в теле, являются локальными, используются непосредственно самой функцией. Для создания глобальной переменной применяется спецификатор global.

def get_older():
global age
age += 1

Поддерживается рекурсия.

def fact(num):
if num == 0:
return 1
else:
return num * fact(num — 1)

Простая функция

Python Функция это блок кода, который начинается с ключевого слова def, с дальнейшим названием функции. Функция может принимать от нуля и более аргументов, ключевые аргументы или сочетание этих аргументов. Функция всегда выдает результат. Если вы не определили, что именно она должна выдавать, она выдаст None. Вот очень простой пример функции, которая выдает строку:

Python

# -*- coding: utf-8 -*-
def a_function():
«»»Обычная функция»»»
return «1+1»

if __name__ == «__main__»:
value = a_function()
print(value)

1
2
3
4
5
6
7
8

# -*- coding: utf-8 -*-

defa_function()

«»»Обычная функция»»»

return»1+1″

if__name__==»__main__»

value=a_function()

print(value)

Все что мы сделали в этом коде, это вызвали функцию и указали значение выдачи. Давайте создадим другую функцию:

Python

# -*- coding: utf-8 -*-
def another_function(func):
«»»
Функция которая принимает другую функцию.
«»»

def other_func():
val = «Результат от %s это %s» % (func(),
eval(func())
)
return val

return other_func

1
2
3
4
5
6
7
8
9
10
11
12
13

# -*- coding: utf-8 -*-

defanother_function(func)

«»»

    Функция которая принимает другую функцию.
    «»»

defother_func()

val=»Результат от %s это %s»%(func(),

eval(func())

)

returnval

returnother_func

Эта функция принимает один аргумент и этот аргумент должен быть функцией или вызываемой. По факту, ее стоит вызывать, используя определенную в прошлом функцию. Вы заметите, что это функция содержит вложенную внутрь функцию, которую мы называем other_func. Она принимает результат переданной функции, её выражение и создает строку, которая говорит нам о том, что мы сделали, после чего она возвращается.

Давайте взглянем на полную версию данного кода:

Python

# -*- coding: utf-8 -*-
def another_function(func):
«»»
Функция которая принимает другую функцию.
«»»

def other_func():
val = «Результат от %s это %s» % (func(),
eval(func())
)

return val
return other_func

def a_function():
«»»Обычная функция»»»
return «1+1»

if __name__ == «__main__»:
value = a_function()
print(value)
decorator = another_function(a_function)
print(decorator())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# -*- coding: utf-8 -*-

defanother_function(func)

«»»

    Функция которая принимает другую функцию.
    «»»

defother_func()

val=»Результат от %s это %s»%(func(),

eval(func())

)

returnval

returnother_func

defa_function()

«»»Обычная функция»»»

return»1+1″

if__name__==»__main__»

value=a_function()

print(value)

decorator=another_function(a_function)

print(decorator())

Так и работает декоратор. Мы создали одну функцию и передали её другой второй функции. Вторая функция является функцией декоратора. Декоратор модифицирует или усиливает функцию, которая была передана и возвращает модификацию. Если вы запустите этот код, вы увидите следующий выход в stdout:

Python

1+1
Результат от 1+1 это 2

1
2

1+1
Результат от 1+1 это 2

Давайте немного изменим код, чтобы превратить another_function в декоратор:

Python

# -*- coding: utf-8 -*-
def another_function(func):
«»»
Функция которая принимает другую функцию.
«»»

def other_func():
val = «Результат от %s это %s» % (func(),
eval(func())
)
return val

return other_func

@another_function
def a_function():
«»»Обычная функция»»»
return «1+1»

if __name__ == «__main__»:
value = a_function()
print(value)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# -*- coding: utf-8 -*-

defanother_function(func)

«»»

    Функция которая принимает другую функцию.
    «»»

defother_func()

val=»Результат от %s это %s»%(func(),

eval(func())

)

returnval

returnother_func
 
 

@another_function

defa_function()

«»»Обычная функция»»»

return»1+1″

if__name__==»__main__»

value=a_function()

print(value)

Обратите внимание на то, что декоратор начинается с символа @, за которым следует название функции, которую мы собираемся «декорировать». Для получения декоратора python, вам нужно только разместить его в строке перед определением функции

Теперь, когда мы вызываем **a_function, она будет декорирована, и мы получим следующий результат:

Python

Результат от 1+1 это 2

1 Результатот1+1это2

Давайте создадим декоратор, который будет делать что-нибудь полезное.

Composition of Decorators

Function decorators are simply wrappers to existing functions. Putting the ideas mentioned above together, we can build a decorator. In this example let’s consider a function that wraps the string output of another function by p tags.

def get_text(name):
   return "lorem ipsum, {0} dolor sit amet".format(name)

def p_decorate(func):
   def func_wrapper(name):
       return "<p>{0}</p>".format(func(name))
   return func_wrapper

my_get_text = p_decorate(get_text)

print(my_get_text("John"))

# <p>Outputs lorem ipsum, John dolor sit amet</p>

That was our first decorator. A function that takes another function as an argument, generates a new function, augmenting the work of the original function, and returning the generated function so we can use it anywhere. To have get_text itself be decorated by p_decorate, we just have to assign get_text to the result of p_decorate.

get_text = p_decorate(get_text)

print(get_text("John"))

# <p>Outputs lorem ipsum, John dolor sit amet</p>

Another thing to notice is that our decorated function takes a name argument. All what we had to do in the decorator is to let the wrapper of get_text pass that argument.

Встроенные декораторы

Существуют встроенные декораторы функций Python. Они поставляются в комплекте с интерпретатором, для их использования требуется импортировать дополнительные модули.

Staticmethod обрабатывает функцию-аргумент так, что она становится статической и принимает спецификатор static.

class C:

@staticmethod

def f(arg1, arg2, …): #static

pass

Classmethod делает из обрабатываемой функции класс.

class MyClass:

@classmethod

def method(cls, arg):

print(‘%s classmethod. %d’ % (cls.__name__, arg))

@classmethod

def call_original_method(cls):

cls.method(5)

def call_class_method(self):

self.method(10)

class MySubclass(MyClass):

@classmethod

def call_original_method(cls):

cls.method(6)

MyClass.method(0) # MyClass classmethod. 0

MyClass.call_original_method() # MyClass classmethod. 5

MySubclass.method(0) # MySubclass classmethod. 0

MySubclass.call_original_method() # MySubclass classmethod. 6

# Вызываем методы класса через объект.

my_obj = MyClass()

my_obj.method(1)

my_obj.call_class_method()

Любая функция может использоваться как декоратор.

Делать много, делая меньше

У декораторов есть общая черта с более ранними метапрограммными абстракциями, введенными в Python: они в действительности не делают ничего, что нельзя было бы сделать и без них. Как Микеле Симионато (Michele Simionato) и я указывали ранее в выпусках рубрики Очаровательный Python, даже в Python 1.5 можно было манипулировать созданием классов без обработчика «metaclass».

Все декораторы похожи своей предельной простотой. Все, что делает декоратор – модифицирует функцию или метод, которые определяются непосредственно после декоратора. Это было возможно всегда, но важнейшим стимулом для использования этой возможности стало появление встроенных функций и в Python 2.2. В «старом стиле» можно было использовать вызов , например, следующим образом:

Листинг 1. Типичный classmethod в «старом стиле»
class C:
    def foo(cls, y):
        print "classmethod", cls, y
    foo = classmethod(foo)

Хотя является встроенным, в нем нет ничего необыкновенного; с тем же успехом можно было бы создать свою собственную функцию трансформации метода. Например:

Листинг 2. Типичный преобразователь метода в «старом стиле»
def enhanced(meth):
    def new(self, y):
        print "I am enhanced"
        return meth(self, y)
    return new
class C:
    def bar(self, x):
        print "some method says:", x
    bar = enhanced(bar)

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

Листинг 3. Типичный classmethod в «старом стиле»
class C:
    @classmethod
    def foo(cls, y):
        print "classmethod", cls, y
    @enhanced
    def bar(self, x):
        print "some method says:", x

Декораторы работают для обычных функций – точно так же, как и для методов в классах. Удивительно, насколько легко такая простая и, строго говоря, не обязательная перемена в синтаксисе улучшает работу и упрощает понимание программ. Декораторы можно объединять в цепочки, располагая их один за другим перед функцией определения метода. Здравый смысл подсказывает избегать нагромождения слишком большого количества декораторов, но использование нескольких подряд вполне уместно:

Листинг 4. Цепочка декораторов
@synchronized
@logging
def myfunc(arg1, arg2, ...):
    # ...do something
# decorators are equivalent to ending with:
#    myfunc = synchronized(logging(myfunc))
# Nested in that declaration order

Будучи просто синтаксической изюминкой, декораторы позволяют легко делать глупости, если вам этого хочется. Декоратор – это просто функция, принимающая как минимум один аргумент. Это программист, использующий декоратор, должен удостовериться, что возвращаемая величина является все еще осмысленной функцией или методом и сохраняет достаточную часть их первоначальных свойств, чтобы результат его действия был полезным. Вот пара примеров неправильного использования синтаксиса:

Листинг 5. Плохой декоратор, который даже не возвращает функцию
>>> def spamdef(fn):
...     print "spam, spam, spam"
...
>>> @spamdef
... def useful(a, b):
...     print a**2 + b**2
...
spam, spam, spam
>>> useful(3, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'NoneType' object is not callable

Другой вариант: декоратор может возвращать функцию, но не имеющую осмысленной связи с недекорированной функцией:

Листинг 6. Декоратор, чья выходная функция игнорирует входную функцию
>>> def spamrun(fn):
...     def sayspam(*args):
...         print "spam, spam, spam"
...     return sayspam

...
>>> @spamrun
... def useful(a, b):
...     print a**2 + b**2
...
>>> useful(3,4)
spam, spam, spam

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

Листинг 7. Декоратор, который изменяет поведение недекорированной функции
>>> def addspam(fn):
...     def new(*args):
...         print "spam, spam, spam"
...         return fn(*args)
...     return new
...
>>> @addspam
... def useful(a, b):
...     print a**2 + b**2
...
>>> useful(3,4)
spam, spam, spam
25

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

Подводя итоги

В общем этот длиннопост переработанный изхабра, создавался когда я врубался в подсмотренный код в django, где в 4 строках скрыто целая тьма смысла и материала для изучения. Там создавался класс mixin в котором декорировался некий метод который обезапасивал доступ к сайту. Ацкая смесь без развернутых пояснений на англ. языке. Ну вот собственно с декораторами вроде разобрался.

Где еще применяется декорирование?

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

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 = 
    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

Скопипащенно отсюда

Добавить комментарий

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