Объяснение класса декоратора в Python - PullRequest
3 голосов
/ 06 июля 2011

Читая о каком-то модуле python, я столкнулся с этим классом декоратора:

# this decorator lets me use methods as both static and instance methods
class omnimethod(object):
        def __init__(self, func):
                self.func = func

        def __get__(self, instance, owner):
                return functools.partial(self.func, instance)

Что я знаю о декораторах, так это то, что может расширять функциональность (например, для функции). Может ли кто-то быть таким добрым и объяснить мне , почему класс выше полезен и , как именно он работает ?

Используется в коде следующим образом:

@omnimethod:
def some_function(...):
    pass

Еще один вопрос:

I encountered this piece of code in the same file:

@property
def some_other_function(...):
    pass

@property не определено нигде в файле. Это какой-то стандартный декоратор? Если да, что это делает? Google не может помочь мне в этом деле.

Кстати, вот источник, где я нашел код: http://code.xster.net/pygeocoder/src/c9460febdbd1/pygeocoder.py

Ответы [ 2 ]

5 голосов
/ 06 июля 2011

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

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

@somedecorator
def somefunc(...):
    pass

# is the same thing as    

def somefunc(...):
    pass
somefunc = somedecorator(somefunc)

так что somefunc на самом деле является omnimethod экземпляром, а не функцией, которая была определена. Интересно, что omnimethod также реализует интерфейс descriptor. Если атрибут класса определяет метод __get__, то всякий раз, когда упоминается этот атрибут, интерпретатор вместо этого вызывает __get__ для этого объект, и возвращает это вместо возврата самого атрибута.

метод __get__ всегда вызывается с экземпляром в качестве первого аргумента и классом этого экземпляра в качестве второго аргумента. Если атрибут действительно был найден в самом классе, то экземпляр будет None.

Последний бит хитрости - functools.partial, который является питонским способом функции curry . когда вы используете partial, вы передаете ему функцию и некоторые аргументы, и она возвращает новую функцию, которая при вызове будет вызывать оригинальную функцию с оригинальными аргументами в дополнение к любым аргументам, которые вы передали позже. omnimethod использует эту технику для заполнения параметра self для функции, которую он переносит.

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

>>> class Foo(object):
...     def bar(self, baz):
...         print self, baz
... 
>>> f = Foo()
>>> f.bar('apples')
<__main__.Foo object at 0x7fe81ab52f90> apples
>>> Foo.bar('quux')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method bar() must be called with 
Foo instance as first argument (got str instance instead)
>>> Foo.bar(None, 'quux')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method bar() must be called with
Foo instance as first argument (got NoneType instance instead)
>>> 

Python предоставляет декоратор bultin classmethod (а также staticmethod, но это не важно), который позволит вам использовать его на уровне класса, но он никогда не увидит экземпляр. всегда получает класс в качестве первого аргумента.

>>> class Foo(object):
...     @classmethod
...     def bar(cls, baz):
...         print cls, baz
... 
>>> f = Foo()
>>> Foo.bar('abc')
<class '__main__.Foo'> abc
>>> f.bar('def')
<class '__main__.Foo'> def
>>> 

В силу своей хитрости omnimethod дает вам немного того и другого.

>>> class Foo(object):
...     @omnimethod
...     def bar(self, baz):
...         print self, baz
... 
>>> f = Foo()
>>> Foo.bar('bananas')
None bananas    
>>> f.bar('apples')
<__main__.Foo object at 0x7fe81ab52f90> apples
>>> 
1 голос
/ 06 июля 2011

omnimethod делает то, что говорит в комментарии;это позволит вам вызывать some_function либо как «статическую функцию» в классе, либо как функцию в экземпляре класса.@property - это стандартный декоратор (см. python docs ), который представляет функцию таким образом, что она выглядит как простая переменная экземпляра.

class B:
  @omnimethod
  def test(self):
    print 1

  @property
  def prop(self):
    return 2

>>> b = B()
>>> b.test()
1
>>> B.test()
1
>>> b.prop
2
...