Search code examples
pythonlistobjectmemorymutability

How to avoid linking of objects in a list?


My problem is quite simple but I am unable to solve it. When I insert objects into a list, the elements of the list all change whenever I change one of them (they all point to the same object in the memory I think). I want to unlink them so the list would not be full of the exactly same objects with the same values. E.g. avoid linking or mutability. I think the problem is how I initialize the objects but I am not sure how to solve it. Here is my code.

from typing import List, Tuple

class State:
    #think of State as some kind of coordinates
    def __init__(self, z:float, angle:float):
        self.z = z
        self.angle = angle

class ListOfStates:
    #this should be an object with a list containing DIFFERENT (unlinked) State objects
    def __init__(self, list_of_states : List[State]):
        self.list_of_states = list_of_states

class StateSettings:
    #a bigger object to encapsulate previous objects
    def __init__(self, state : State, list_of_states : ListOfStates):
        self.state = state
        self.list_of_states = list_of_states
some_number = 42

# my try #1
state_settings = StateSettings
#create a list of State objects to be used later
state_settings.list_of_states = [State for i in range(some_number)]
state_settings.state = State
for i in range(some_number):
    state_settings.list_of_states[i].angle = i

And state_settings.list_of_states contains the same copy of the object 42 times, e.g.

print(state_settings.list_of_states[0].angle)
print(state_settings.list_of_states[1].angle)
print(state_settings.list_of_states[2].angle)

prints

41
41
41

I also tried different ways to initialize, but with no luck.

# my try #2
state_settings = StateSettings(
    state = State(
        z = 0,
        angle = 0),
    list_of_states = [State for i in range(some_number)]
)
for i in range(some_number):
    state_settings.list_of_states[i].angle = i

or

# my try 3
from copy import deepcopy
state_settings = StateSettings
state_settings.list_of_states = [deepcopy(State) for i in range(some_number)]
state_settings.state = deepcopy(State)
for i in range(some_number):
    state_settings.list_of_states[i].angle = i

My question, as far as I know, is not solved by answers such as Changing a single object within an array of objects changes all, even in a different array or List of Objects changes when the object that was input in the append() function changes.


Solution

  • There are some fundamental mistakes you have made in the code. Let me try to put some light on those first , using your lines of code

    # my try #1
    state_settings = StateSettings
    

    What you did in the above line is that, you assigned the class StateSettings to state_settings variable. You never created an object here.

    #create a list of State objects to be used later
    state_settings.list_of_states = [State for i in range(some_number)]
    

    What you did here is also same, created a list of State class references, not objects. So, all the values in list are same.

    state_settings.state = State
    

    What you did here, is set an attribute state to StateSettings class , not the object.

    for i in range(some_number):
        state_settings.list_of_states[i].angle = i
    

    What you did here, set an attribute angle the class State. Since all values in the list are same State references, everywhere value will be same

    To summarize the above said issues,

    When you assign an attribute to the class name, attribute gets added to the class itself. Any where you have a reference to that class will have the same attribute value. When you create an object and then set an attribute on the object, the attribute lies only in that object. Its not reflected on other objects created.

    A simple update on the code you wrote is below, which I guess works like you want.

    from typing import List
    
    
    class State:
        # think of State as some kind of coordinates
        # Use default values, so you dont need to provide a value in init
        def __init__(self, z: float = None, angle: float = None):
            self.z = z
            self.angle = angle
    
    
    class ListOfStates:
        # this should be an object with a list containing DIFFERENT (unlinked) State objects
        # Use default values, so you dont need to provide a value in init
        def __init__(self, list_of_states: List[State] = None):
            self.list_of_states = list_of_states
    
    
    class StateSettings:
        # a bigger object to encapsulate previous objects
        # Use default values, so you dont need to provide a value in init
        def __init__(self, state: State = None, list_of_states: ListOfStates = None):
            self.state = state
            self.list_of_states = list_of_states
    
    
    some_number = 42
    
    # my try #1
    state_settings = StateSettings()
    # create a list of State objects to be used later
    state_settings.list_of_states = [State() for i in range(some_number)]
    state_settings.state = State()
    for i in range(some_number):
        state_settings.list_of_states[i].angle = i