Вопрос

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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top