Let's take as example this classical solution to the problem of updating dependent object attributes:
class SomeClass(object):
def __init__(self, n):
self.list = range(0, n)
@property
def list(self):
return self._list
@list.setter
def list(self, val):
self._list = val
self._listsquare = [x**2 for x in self._list ]
@property
def listsquare(self):
return self._listsquare
@listsquare.setter
def listsquare(self, val):
self.list = [int(pow(x, 0.5)) for x in val]
It works as required: when a new value is set for one attribute, the other attribute is updated:
>>> c = SomeClass(5)
>>> c.listsquare
[0, 1, 4, 9, 16]
>>> c.list
[0, 1, 2, 3, 4]
>>> c.list = range(0,6)
>>> c.list
[0, 1, 2, 3, 4, 5]
>>> c.listsquare
[0, 1, 4, 9, 16, 25]
>>> c.listsquare = [x**2 for x in range(0,10)]
>>> c.list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
But, what if we mutate the attribute list instead of setting it to a new value?:
>>> c.list[0] = 10
>>> c.list
[10, 1, 2, 3, 4, 5, 6, 7, 8, 9] # this is ok
>>> c.listsquare
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] # we would like 100 as first element
We would like listsquare attribute to be updated accordingly, but it's not the case because the setters are not invoked when we mutate the list attribute.
Of course we could force the update by explicitly invoking the setter after we modify the attribute, for example by doing:
>>> c.list[0] = 10
>>> c.list = c.list. # invoke setter
>>> c.listsquare
[100, 1, 4, 9, 16, 25, 36, 49, 64, 81]
but it looks somewhat cumbersome and error prone for the user, we would prefer that it occurs implicitly.
What would be the most pythonic way for having the attributes updated when another mutable attribute is modified. How the object can know that one of his attributes has been modified?