Search code examples
pythonoopattributesdecoratorpython-decorators

When setting variable to object, set it to all nested objects within


I have the following code that works:

class fundamental_object():

    def __init__(self,x):
        self.x = x

class encapsulator_object():

    def __init__(self,x,obj_list):
        self._x = x
        self.obj_list = obj_list

    @property
    def x(self):
        return self.x

    @x.setter
    def x(self,new_x):
        print('in setter!')
        self._x = new_x
        for obj in self.obj_list:
            obj.x = new_x

if __name__ == '__main__' :
    x = 10
    obj_1 = fundamental_object(x)
    obj_2 = fundamental_object(x)
    obj_list = [obj_1,obj_2]
    encapsulator = encapsulator_object(x,obj_list)

    encapsulator.x = 20
    print(encapsulator._x)
    print(obj_1.x) # all of these are also updated to 20.

As you can see, the idea is that, whenever I change the attribute "x" of the encapsulator object, I want all nested objects inside it (fundamental_objects) to also be updated with this new variable "x". However, from a user standpoint, this gets confusing really quickly, since, if I understand correctly, "x" is an integer for "fundamental_object", whereas "x" is a method for "encapsulator_object", and to actually access the integer in the encapsulator I would need to use "_x". Is there any easy/correct/pythonic way to make it so the following works :

    x = 10
    obj_1 = fundamental_object(x)
    obj_2 = fundamental_object(x)
    obj_list = [obj_1,obj_2]
    encapsulator = encapsulator_object(x,obj_list)

    encapsulator.x = 20
    print(encapsulator.x) # notice the underscore is now gone!  
    print(obj_1.x)        # this would be updated to 20 as well

I understand that it is possible to make it so "fundamental_objects" also have "_x" as the integer variable, which would somewhat reduce the confusion, but still, I'd like to completely get rid of the underscores if possible! (right now I get an infinite recursion). Thanks!


Solution

  • Check this code. I only changed your getter method in your property. Now it is pointing to the self._x.

    class fundamental_object():
        def __init__(self, x):
            self.x = x
    
    
    class encapsulator_object():
        def __init__(self, x, obj_list):
            self._x = x
            self.obj_list = obj_list
    
        @property
        def x(self):
            return self._x   # -----> here
    
        @x.setter
        def x(self, new_x):
            print('in setter!')
            self._x = new_x
            for obj in self.obj_list:
                obj.x = new_x
    
    
    if __name__ == '__main__':
        x = 10
        obj_1 = fundamental_object(x)
        obj_2 = fundamental_object(x)
        obj_list = [obj_1, obj_2]
        encapsulator = encapsulator_object(x, obj_list)
    
        encapsulator.x = 20
        print(encapsulator.x)  # notice the underscore is now gone!
        print(obj_1.x)  # this would be updated to 20 as well
    

    As an alternative you can completely remove x or _x in encapsulator_object. Then in your getter you can find x within the self.obj_list :

    class fundamental_object():
        def __init__(self, x):
            self.x = x
    
    
    class encapsulator_object():
        def __init__(self, obj_list):
            self.obj_list = obj_list
    
        @property
        def x(self):
            return self.obj_list[0].x
    
        @x.setter
        def x(self, new_x):
            print('in setter!')
            for obj in self.obj_list:
                obj.x = new_x
    

    Bear in mind that, in this example because we decided to pick first item in the list, all objects must have the same x value. There is no need to worry about it after you can the setter though. I mentioned it if you want to call the getter before setter.