views:

87

answers:

1

I want to create a decorator that allows me to refer back to the decorated object and grab another decorator from it, the same way you can use setter/deleter on properties:

@property
def x(self):
    return self._x

@x.setter
def x(self, y):
    self._x = y

Specifically, I'd like it to act basically the same as property, but emulate a sequence instead of a single value. Here's my first shot at it, but it doesn't seem to work:

def listprop(indices):
    def dec(func):
        class c(object):
            def __init__(self, l):
                self.l = l
            def __getitem__(self, i):
                if not i in self.l:
                    raise Exception("Invalid item: " + i)
                return func(i)
            @staticmethod
            def setter(func):
                def set(self, i, val):
                    if not i in self.l:
                        raise Exception("Invalid item: " + i)
                    func(i, val)
                c.__setitem__ = set
        return c(indices)
    return dec

# ...
class P:
    @listprop(range(3))
    def prop(self, i):
        return get_prop(i)

    @prop.setter
    def prop(self, i, val):
        set_prop(i, val)

I'm pretty sure that c.__setitem__ = set is wrong, but I can't figure out how to get a reference to the instance at that point. Ideas?

Alex Martelli's solution works on 2.6, but something about it is failing on 2.4 and 2.5 (I'd prefer to have it work on these older versions as well, though it's not strictly necessary):

2.4:

>>> p = P()
>>> p.prop
>>> p.prop[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: unsubscriptable object

2.5:

>>> p = P()
>>> p.prop
>>> p.prop[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is unsubscriptable

2.6:

>>> p = P()
>>> p.prop
<__main__.c object at 0x017F5730>
>>> p.prop[0]
0
+3  A: 

I fixed many little details and the following version seems to work as you require:

def listprop(indices):
    def dec(func):
        class c(object):
            def __init__(self, l, obj=None):
                self.l = l
                self.obj = obj
            def __get__(self, obj, cls=None):
                return c(self.l, obj)
            def __getitem__(self, i):
                if not i in self.l:
                    raise Exception("Invalid item: " + i)
                return func(self.obj, i)
            def setter(self, sfunc):
                def doset(self, i, val):
                    if not i in self.l:
                        raise Exception("Invalid item: " + i)
                    sfunc(self.obj, i, val)
                c.__setitem__ = doset
                return self
        result = c(indices)
        return result
    return dec

# ...
class P:
    @staticmethod
    def get_prop(i): return i*100

    @staticmethod
    def set_prop(i, val): print 'set %s to %s' % (i, val)

    @listprop(range(3))
    def prop(self, i):
        return self.get_prop(i)

    @prop.setter
    def prop(self, i, val):
        self.set_prop(i, val)

As you see, assigning to c.__setitem__ was not the problem -- there were others, such as c lacking a __get__ (so it wasn't a descriptor, see this guiide) and setter returning None (so p.prop ended up as None).

Alex Martelli