deckardk
deckardk

Reputation: 25

Python: setting values of a list of nested dictionaries in class instances

I have a burning question that I just can't figure out. Let's say you have a class, and this class takes in as input a list of nested dictionaries. It initializes one of the keys of each of these dictionaries with a new, empty dictionary. Later, I want to set one of the nested values of an object in the list as something. For some reason this seems to affect other objects in the list?

I know that's sounds very convoluted, so here's an example:

class Tester():
    def __init__(self, stuff):

        # stuff is a list of dictionaries
        self.stuff = stuff

        # Each item in the list should be initialized to this dictionary
        inside_init_dict = {'test': True}
        for x in self.stuff:
            x['info'] = inside_init_dict

if __name__ == '__main__':
    new_stuff = [{'info': {}}, {'info': {}}, {'info': {}}]
    mytest = Tester(new_stuff)

    print(mytest.stuff)
    # >>> [{'info': {'test': True}}, {'info': {'test': True}}, {'info': {'test': True}}]

    # I want to just set a value in the nested dict of the first item in the list
    mytest.stuff[0]['info']['test'] = False

    # However, all items in the list change
    print(mytest.stuff)
    # >>> [{'info': {'test': False}}, {'info': {'test': False}}, {'info': {'test': False}}]

This happens on both Python 2 and 3. The only way I can get around this is to not use the separate variable "inside_init_dict", and directly set the initialized dictionary:

class Tester():
    def __init__(self, stuff):

        # stuff is a list of dictionaries
        self.stuff = stuff

        # Each item in the list should be initialized to this dictionary
        for x in self.stuff:
            x['info'] = {'test': True}

if __name__ == '__main__':
    new_stuff = [{'info': {}}, {'info': {}}, {'info': {}}]
    mytest = Tester(new_stuff)

    print(mytest.stuff)
    # >>> [{'info': {'test': True}}, {'info': {'test': True}}, {'info': {'test': True}}]

    mytest.stuff[0]['info']['test'] = False

    # This is what I want
    print(mytest.stuff)
    # >>> [{'info': {'test': False}}, {'info': {'test': True}}, {'info': {'test': True}}]

What's going on here? I have tried setting the variable "inside_init_dict" in various places, like as a class variable or outside the class. The issue still occurs.

Upvotes: 0

Views: 1040

Answers (3)

Samira N
Samira N

Reputation: 163

This happens because dicts are mutable, meaning that you can change their content without changing their identity. Here's a much simpler example of the behavior you're seeing:

my_dict = { "key" : "value" }
my_list = [ my_dict, my_dict ]
my_list[0]["key"] = "new_value"
print(my_list) # [ {"key" : "new_value"}, {"key": "new_value"} ]

Why this happens:

In the first line of this code, I create a new dictionary, {"key" : "value"}, and assign the name my_dict to it.

In the second line, I create a list, whose zeroth and first element both point to my_dict.

In the third line, I access my_dict (through my_list[0]), and I mutate it: changing the value associated with "key".

In the fourth line, I check the value of my_list. Both the zeroth and the first element of my_list still point to my_dict -- and we've changed my_dict. So the change is reflected in both elements of the list.

One way to fix it:

Instead pointing to the same dictionary twice, create two dictionaries that have the same value:

my_list = [ { "key" : "value" } , { "key" : "value" } ] 
my_list[0]["key"] = "new_value"
print(my_list) # [ {"key" : "new_value"}, {"key": "value"} ]

Upvotes: 2

Alex Hall
Alex Hall

Reputation: 36033

In the first example you create a single dictionary inside_init_dict outside the loop and put it in multiple places. Every element of the list gets that same inside_init_dict. What you're seeing is not other objects in a list being affected, there's just one object being shown multiple times.

In the second example:

    for x in self.stuff:
        x['info'] = {'test': True}

Now each x gets its own dictionary. They all have the same value at first, but they are different instances, like identical twins.

Upvotes: 3

Moses Koledoye
Moses Koledoye

Reputation: 78556

Assign the keys to different copies of the inside_init_dict dictionary instead of the same one:

    ...
    inside_init_dict = {'test': True}
    for x in self.stuff:
        x['info'] = inside_init_dict.copy()

Upvotes: 1

Related Questions