pythonpython-3.xoopobjectattributes

In python, how to update attribute based on attribute of another object passed as an argument?


Alright, let's start with the code, because it's pretty explicit :

class c_1:
    def __init__(self):
        self.a = 5

class c_2:
    def __init__(self,other_obj):
        self.other = other_obj
        self.b = self.other.a

And the output:

obj_1 = c_1()
obj_2 = c_2(obj_1)
print(obj_2.b)
# Output is 5
obj_1.a = 8
print(obj_2.b)
# Output is still 5

This is the problem. I know the second call of obj_2.b should return 5, but I want it to return 8.

I think that what I actually want, is for the value of obj_1.a to be passed by reference for obj_2.b (this example is pretty simple, but in my actual code, there are more attributes from obj_1 that obj_2 uses.)

Is there a way, without calling another method, to automatically update obj_2.b when the value of obj_1.a gets changed ? Thank you.


Solution

  • It is possible, but your mileage may vary, depending on your use cases:

    One approach, shown hereunder, is to implement a Var class that handles this for you; encapsulating the values inside an object circumvents the 'passing by value', and opens the 'values' to mutability. here is an incomplete (and brittle) example; there are quite a few corner cases to iron out to make it work smoothly, but to answer your question: yes, it is definitely possible:

    Other approaches might use the inspect module, or metaclasses, or the implementation of callbacks.

    tkinter in the python standard library uses specialized classes, and a callback approach.

    class Var:
        def __init__(self, value):
            self._value = value
    
        @property
        def value(self):
            return self._value
    
        @value.setter
        def value(self, value):
            self._value = value
    
        def __repr__(self):
            return str(self._value)
    
    
    class IntVar(Var):
    
        def __iadd__(self, other: int):
            self._value = self._value + other
            return self.value
    
    
    class c_1:
        def __init__(self):
            self._a = IntVar(5)
    
        @property
        def a(self):
            return self._a
    
        @a.setter
        def a(self, value):
            self._a.value = value
    
    
    class c_2:
        def __init__(self, other_obj):
            self.other = other_obj
            self.b = self.other.a
    
    
    obj_1 = c_1()
    obj_2 = c_2(obj_1)
    print(obj_2.b)
    obj_1.a = 8
    print(obj_2.b)
    obj_1.a += 2
    print(obj_2.b)
    

    Output:

    5
    8
    10