Python functions act as descriptors, which means that whenever you access a function on a class or instance, their .__get__()
method is invoked and a method object is returned which keeps a reference to the original function, and for instances, a reference to the instance. Method object then acts as wrappers; when called they call the underlying function and pass in the instance reference as self
.
Your callable class object, on the other hand, does not implement the descriptor protocol, it has no .__get__()
method, and thus it never is given an opportunity to bind to the instance. You'll have to implement this functionality yourself:
class Decor(object):
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if instance is None:
return self
d = self
# use a lambda to produce a bound method
mfactory = lambda self, *args, **kw: d(self, *args, **kw)
mfactory.__name__ = self.func.__name__
return mfactory.__get__(instance, owner)
def __call__(self, instance, *args, **kwargs):
def closure(*args, **kwargs):
print instance, args, kwargs
return self.func(instance, *args, **kwargs)
return closure(*args, **kwargs)
Demo:
>>> class Victim(object):
... @Decor
... def sum(self, a, b):
... return a+b
...
>>> v = Victim()
>>> v.sum
<bound method Victim.sum of <__main__.Victim object at 0x11013d850>>
>>> v.sum(1, 2)
<__main__.Victim object at 0x11013d850> (1, 2) {}
3
It is not a good idea to store the instance you are bound to directly on the Decor
instance; this is a class attribute, shared among instances. Setting self.instance
is neither thread-safe nor allows methods to be stored for later invocation; the most recent __get__
call will alter self.instance
and lead to hard-to-resolve bugs.
You can always return a custom proxy object instead of a method:
class DecorMethod(object):
def __init__(self, decor, instance):
self.decor = decor
self.instance = instance
def __call__(self, *args, **kw):
return self.decor(instance, *args, **kw)
def __getattr__(self, name):
return getattr(self.decor, name)
def __repr__(self):
return '<bound method {} of {}>'.format(self.decor, type(self))
and use that in your Decor.__get__
instead of producing a method:
def __get__(self, instance, owner):
if instance is None:
return self
return DecorMethod(self, instance)
The DecorMethod
here passes any requests for unknown attributes back to the Decor
decorator instance:
>>> class Victim(object):
... @Decor
... def sum(self, a, b):
... return a + b
...
>>> v = Victim()
>>> v.sum
<bound method <__main__.Decor object at 0x102295390> of <class '__main__.DecorMethod'>>
>>> v.sum.func
<function sum at 0x102291848>