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 omni
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 "", line 1, in
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 "", line 1, in
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')
abc
>>> f.bar('def')
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
>>>