raphaelts
raphaelts

Reputation: 381

In class object, how to auto update attributes when you change only one entry of a list?

I have seen this answer for a very similar question:

In class object, how to auto update attributes?

I will paste the code here:

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]

>>> 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]

In this code, when I update the list using:

>>> c.list = [1, 2, 3, 4]

c.listsquare will be updated accordingly:

>>> c.listsquare
[1, 4, 9, 16]

But when I try:

>>> c.list[0] = 5
>>> c.list
[5, 2, 3, 4]

Listsquares is not updated:

>>> c.listsquare
[1, 4, 9, 16]

How can I make listsquare auto update when I change only one item inside the list?

Upvotes: 0

Views: 1643

Answers (2)

Nf4r
Nf4r

Reputation: 1410

First of all I would recommend u not to change two, different attributes by accesing single list.setter. The reason why does:

>>> c.list[0] = 5
>>> c.list
[5, 2, 3, 4]
>>> c.listsquare
[1, 4, 9, 16]

not work, is that, you are accesing the c.list.__setitem__ method.

c.list[0] = 5 
is equal to
c.list.__setitem__(0, 5)
or
list.__setitem__(c.list, 0, 5)
and as such, the list.__setitem__ method isn't the one you've implemented in your class.

However, if you really want to do so, you should reconsider creating the square list based on self._list.

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

    @property
    def listsquare(self):
        return [n ** 2 for n in self.list]

    @listsquare.setter
    def listsquare(self, val):
        self.list = [int(pow(x, 0.5)) for x in val]

Upvotes: 1

martineau
martineau

Reputation: 123473

One way you could do it is by having a private helper _List class which is almost exactly like the built-in list class, but also has a owner attribute. Every time one of a _List instance's elements is assigned a value, it can then modify the listsquare property of its owner to keep it up-to-date. Since it's only used by SomeClass it can be nested inside of it to provide even more encapsulation.

class SomeClass(object):
    class _List(list):
        def __init__(self, owner, *args, **kwargs):
            super(SomeClass._List, self).__init__(*args, **kwargs)
            self.owner = owner

        def __setitem__(self, index, value):
            super(SomeClass._List, self).__setitem__(index, value)
            self.owner.listsquare[index] = int(value**2)

    def __init__(self, n):
        self.list = SomeClass._List(self, 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]

c = SomeClass(5)
print(c.list)        # --> [0, 1, 2, 3, 4]
print(c.listsquare)  # --> [0, 1, 4, 9, 16]
c.list[0] = 5
print(c.list)        # --> [5, 1, 2, 3, 4]
print(c.listsquare)  # --> [25, 1, 4, 9, 16]

Upvotes: 1

Related Questions