Search code examples
pythonclassattributesdecorator

How do I call a specific function when a class variable changes?


I've created a class and initialized three variables a, b and c. Now I want to call a specific function func1 whenever the variables a or c are changed from the outside, and a function func2 when variable b is changed from the outside.

I am aware that this can be done using decorators, like this:

class Event:
    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    @property
    def a(self):
        return self._a
    @a.setter
    def a(self, value):
        self._a = value
        print("Variable a changed!")
        self.func1()

    @property
    def b(self):
        return self._b
    @b.setter
    def b(self, value):
        self._b = value
        print("Variable b changed!")
        self.func2()

    @property
    def c(self):
        return self._c
    @c.setter
    def c(self, value):
        self._c = value
        print("Variable c changed!")
        self.func1()

    def func1(self):
        print("Function 1 called")
    def func2(self):
        print("Function 2 called")

obj = Event(1, 2, 3)
obj.a = 15
obj.b = 10
obj.c = 5

My final code will have 8 or more variables however, and writing a designated @property and @var.setter for every single one of them will be very cumbersome and not really readable.

Is there a simpler way to just say If variables a, c, f, ... are updated, call function X, if b, e, ... are updated, call function Y?

Thank you!


Solution

  • You could subclass property with custom features. Here a basic example on how to do that. Notice you should provide a dictionary which maps which property triggers which function (as string).

    class TriggerProperty(property):
    
        MAPPER = {'a': 'func1', 'b': 'func2', 'c': 'func1'}
        
        def __set__(self, obj, value):
            super().__set__(obj, value)
            func_name = self.MAPPER.get(self.fget.__name__)
            getattr(obj, func_name)()
    
    
    class Event:
        # ...
        @TriggerProperty
        def a(self):
            return self._a
    
        @a.setter
        def a(self, value):
            self._a = value
            print("Variable a changed!")
    
        # ...