Is it possible to have a decorator that makes a method work like an attribute if values are assigned to it using class.something = 2 and work like a method if it is called like class.something(2, True)?
What I currently have
To be more concrete, I currently have the following
class attributeSetter(object):
''' Makes functions appear as attributes. Takes care of autologging.'''
def __init__(self, func):
self.func = func
def __set__(self, obj, value):
return self.func(obj, value)
def __repr__(self):
return repr(self.__getattribute__)
So with this class with a decorated method:
class myClass(object):
@attributeSetter # decorated!
def myAttrib(self, value, add=False, subtract=False):
if add:
value += 2
if subtract:
value -= 2
self.__dict__['myAttrib'] = value
I can do this:
instance = myClass()
instance.myAttrib = 2 # sets the value to 2
instance.myAttrib *= 4 # now 8
print instance.myAttrib # 8
What I want
But I want, in addition, to be able to do this:
instance.myAttrib(3, add=True) # now 8 + 3 + 2 = 13
instance.myAttrib(0, subtact=True) # now 13 + 0 - 2 = 11
print instance.myAttrib # 11
My hunch is that I just have to add a __call__ to the attributeSetter decorator like this:
def __call__(self, *args, **kwargs):
self.func(*args, **kwargs)
but then it won't let me set values using the "=" syntax.
Why?
I'm co-developing PsychoPy (a python module for stimulus delivery in psychphysics) and we want do to some processing when each stimulus parameter is set, hence the decorator to avoid setter/getter methods. We log each attribute change by default but in some cases, users want to disable logging of this particular setting with an extra log=False argument, or provide more arguments. Hence it would be nice in those cases if could just use the attribute like a function/method, which it really is in the first place.
解决方案
You'll need to implement a __get__ method returning a callable; your decorator already provides a descriptor object, simply make it work when accessing as an attribute.
This can be as simple as turning around and binding the wrapped function (so you get a bound method):
class attributeSetter(object):
''' Makes functions appear as attributes. Takes care of autologging.'''
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
return self.func.__get__(instance, owner)
def __set__(self, obj, value):
return self.func(obj, value)
However, this makes it incompatible with simply accessing the attribute! instance.myAttrib now returns a bound method! You cannot have it both ways here; methods are simply bound attributes (so attributes that passed through the descriptor protocol), that happen to be callable.
You could of course return a proxy object from __get__; one that implements a __call__ method and otherwise tries to act as much as possible as the underlying managed instance attribute (what your function stored in self.__dict__['myAttrib']); but this path is fraught with problems as a proxy object can never truly be the underlying attribute value.