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
精彩评论