Search code examples
pythonclassgetter-setter

How to detect changes of a list whithin a Python class, or why does "setter" not trigger


I would like to have a list in a Python class. Whenever an element in the list is changed I need to run some logic. I'm pretty new to classes in python and my approach with the setter might be pretty naive. This is what makes intuitive sense to me:

class test():
    def __init__(self):
        self._R = [False]*16

    @property
    def R(self):
        return self._R

    @R.setter
    def R(self,a):
        print('Why do I not get reached?')
        self._R = a

W = test()
W.R[0] = True

But the setter never gets triggered. If you could give me a notch in the right direction, I would be very great full.


Solution

  • You can create a new List-like class that takes a callback function and executes it whenever the list is changed:

    class CallbackList:  # PEP-8 style suggests UpperCase class names
        def __init__(self, callback=None):
            self._list = [False]
            self._callback = callback  # Python functions are first-class objects just like ints, strings, etc, so this is completely legal
    
        def __setitem__(self, index, value):
            self._list[index] = value
            if self._callback:
                self._callback()  # Executes the callback function whenever a value is set
    
        def __getitem__(self, index):
            return self._list[index]
    
    
    class Test:
        def __init__(self):
            self.callback_list = CallbackList(callback=self.foo)
    
        def foo(self):
            print("You changed the list!")
    
    
    W = Test()
    W.callback_list[0] = True  # This prints "You changed the list!"
    

    Note that this still won't catch every possible change. For example:

    W = Test()
    some_list = [1, 2, 3]
    
    W.callback_list[0] = some_list  # This triggers the callback function
    print(W.callback_list[0])  # [1, 2, 3]
    
    some_list.append(4)  # This does NOT trigger the callback function!
    print(W.callback_list[0])  # [1, 2, 3, 4] !