开发者

Explanation of a decorator class in python

开发者 https://www.devze.com 2023-03-17 13:23 出处:网络
While reading up about some python module I encountered this decorator class: # this decorator lets me use methods as both static and instance methods

While reading up about some python module I encountered this decorator class:

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

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

What I know about decorators, is that the can extend functionality (e.g. for a function). Could someone be so kind and explain to me why the class above is useful and how it exactly works?

It is used in the code this way:

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

Another question:

I encountered this piece of code in the same file:

@property
def some_other_function(...):
    pass

@property is not defined anywhere in the file. Is this some standard decorator? If yes, what does it do? Google couldn't help me on this case.

By the way, here is the source where I found the code: http://code.xster.net/pygeocoder/src/c9460febdbd1/pygeocoder.py


that omnimethod is very clever. It uses some very subtle tricks to do it's job. Let's start at the beginning.

You probably already know that decorator syntax is just sugar for function application, ie:

@somedecorator
def somefunc(...):
    pass

# is the same thing as    

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

so somefunc is actually an omnimethod instance, not the function that had been defined. what's interesting about this is that omnimethod also implements the descriptor interface. If a class attribute defines a __get__ method, then whenever that attribute is mentioned, the interpreter instead calls __get__ on that object, and returns that instead of returning the attribute itself.

the __get__ method is always called with instance as first argument, and the class of that instance as second argument. If the attribute was actually looked up from the class itself, then instance will be None.

The last bit of trickery is functools.partial, which is the python way of function currying. when you use partial, you pass it a function and some arguments, and it returns a new function, that when called, will call the original function with the original arguments in addition to whichever arguments you passed in later. omnimethod uses this technique to populate the self parameter to the function it wraps.

Here's what that looks like. a regular method can be called when you read it from an instance but you can't use it from the class itself. you get an unbound 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 provides a bultin decorator classmethod (and also staticmethod, but nevermind that), that will allow you to use it at the class level, but it never gets to see the instance. it always recieves the class as it's first argument.

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

By it's bit of cleverness, omnimethod gives you a little bit of both.

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


omnimethod does what it says in the comment; it will let you call some_function as either a 'static function' on a class or as a function on an instance of the class. @property is a standard decorator (see the python docs) that exposes a function in a way that makes it look like a simple instance variable.

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
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号