pythonpython-3.xdecoratordescriptor

Decorate setter property of a class


I have a class Tracker defined as below:

class Tracker():
    def __init__(self):
        self._value = None

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value

Then I have another class Aggregator, defined as below. This class takes instances of Tracker defined above.

class Aggregator():
    def __init__(self,trackers):
        self.trackers = trackers

        def updator(func):
            def wrapper(*args): # this function will be used to wrap the Tracker.value property/descriptor
                return_value = func(*args)
                self.update() # Call Aggregator.update() method
                return return_value
            return wrapper

        for t in trackers: # list of tracker objects
            t.value = updator(t.value) # Causes errors since the t.value no longer references the method but is used to get/set the value 
            t.__class__.__dict__['value'].fset = updator(t.__class__.__dict__['value'].fset) # Causes AttributeError: readonly attribute


    def update(self):
        self.value = sum([t.value for t in self.trackers])

Trying to decorate the Tracker.value in the Aggregator class causes errors since the Tracker.value no longer references the method but is used to get/set the value. My problem is this: how do I decorate the Tracker.value (property/descriptor) so updating a value in any tracker will trigger the Aggregator.update method of the object that includes that tracker?


Solution

  • Thanks for your inputs @juanpa.arrivillaga. If I understand you correctly, this will cause updates on all the Aggregator instances to be triggered for any Tracker instance update, not only for the grouped ones (which is what I want).

    t1 = Tracker()
    t2 = Tracker()
    a1 = Aggregator([t1,t2])
    t1.value = 32
    t2.value = 55
    
    t3 = Tracker()
    t4 = Tracker()
    a2 = Aggregator([t3,t4])
    t3.value = 29 # will trigger updates on a1 and a2!
    t4.value = 37 # # will trigger updates on a1 and a2!
    

    The solution that worked for me was to do the following:

    class Tracker():
        def __init__(self):
            self._value = None
    
        @property
        def value(self):
            return self._value
    
        @value.setter
        def value(self, value):
            self._value = value
            if hasattr(self,'aggregator_update') and callable(self.aggregator_update):
                self.aggregator_update()
    
    class Aggregator():
        def __init__(self,trackers):
            self.trackers = trackers
            for t in self.trackers:
                t.aggregator_update = self.update