Reputation: 25
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
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
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
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