0

I'm looking for a way to have the setter function for x run when a user changes a value in the dictionary test.x. Here setting the whole dictionary will run the setter but if a user attempts to make test.x['A'] = 3 the setter is ignored. How do I get this working? ..or am I doing this completely wrong?

class Foo(object):
    def __init__(self):
        self._x = None
    
    @property
    def x(self):
        print "Getting attribute x"
        return self._x
    
    @x.setter
    def x(self, value):
        print "Setting attribute x"
        self._x = value

test = Foo()
test.x = {'A':1, 'B':2}
test.x['A'] = 3
test = Foo()
test.x = {'A':1, 'B':2}
Setting attribute x
test.x['A'] = 3
Getting attribute x
Antony
  • 1

2 Answers2

0

On test.x['A'] = 3 you not actually setting a new object or value for x. You only getting the value (thats why the getter is called) and changing it (not setting it).

For change detection of the dictionary have a look at this: How to detect if any element in a dictionary changes?

Ayk Borstelmann
  • 316
  • 2
  • 8
0

Setting an item from dictionary x is a completely different sort of operation from setting x -- it is not modifying the Foo instance itself but rather it is modifying the dictionary to which Foo contains a reference.

What you could do is when you set x, you could set it to an instance of a subclass of dict which overrides __setitem__ in whatever way you want. In this example, it would run the specified callback function before setting the item.

class MyDict(dict):

    """
    a MyDict is like a dict except that when you set an item, before 
    doing so it will call a callback function that was passed in when the 
    MyDict instance was created
    """

    def __init__(self, value, setitem_callback=None, *args, **kwargs):
        super(MyDict, self).__init__(value, *args, **kwargs)
        self._setitem_callback = setitem_callback
        
    def __setitem__(self, key, value):
        if self._setitem_callback:
            self._setitem_callback(key, value)
        super(MyDict, self).__setitem__(key, value)
    

class Foo(object):
    def __init__(self):
        self._x = None
    
    @property
    def x(self):
        print "Getting attribute x"
        return self._x

    def _x_setitem(self, key, value):
        print "Setting key {} of x".format(key)
    
    @x.setter
    def x(self, value):
        print "Setting attribute x"
        if isinstance(value, dict):
            # caller tries to set it to a dict, but we set it to a MyDict instance instead
            value = MyDict(value, setitem_callback=self._x_setitem)
        self._x = value


        
test = Foo()
test.x = {'A':1, 'B':2}
test.x['A'] = 3
print test.x

Gives:

Setting attribute x
Getting attribute x
Setting key A of x
Getting attribute x
{'A': 3, 'B': 2}
alani
  • 12,573
  • 2
  • 13
  • 23