Why does changing one Python list element change all the others?

137 Views Asked by At

I have some code where I make a dictionary and each value is the list [0,0]. I found that incrementing any list index would increment each value's corresponding index; i.e. d[key][0] += 1 would increment each other list's 0 index.

I assumed Python's shallow copying of the list was to blame, since my dictionary was constructed using dict.fromkeys(keys, [0,0]). However, I had the same problem when using copy.deepcopy on the list. I then tried dictionary comprehension: {key:[0,0] for key in keys}. This works as I would expect; modifying one list does not affect the others.

Since I've found a solution to this issue, my actual question is why this is the case. It seems that either I'm mistaken about how deepcopy works or that it's irrelevant to the issue at hand, but I don't know what else would cause this.

This is a full snippet that reproduces the behavior:

import copy
keys = [i for i in range(5)]
l = [0,0]
d = dict.fromkeys(keys, copy.deepcopy(l))
#d = {key:[0,0] for key in keys} # this approach works as expected
d[0][0] += 1
print(d)
# expected: {0: [1, 0], 1: [0, 0], 2: [0, 0], 3: [0, 0], 4: [0, 0]}
# actual: {0: [1, 0], 1: [1, 0], 2: [1, 0], 3: [1, 0], 4: [1, 0]}
3

There are 3 best solutions below

0
Luke B On BEST ANSWER

In this case, deepcopy is not helping the issue, it's dict.fromkeys functionality. From the documentation at https://docs.python.org/3/library/stdtypes.html#dict.fromkeys

All of the values refer to just a single instance, so it generally doesn’t make sense for value to be a mutable object such as an empty list. To get distinct values, use a dict comprehension instead.

You are passing in a single list to fromkeys for the value argument. So every key in the dict refers to one list, which just happened to be deepcopied from l.

So it seems like your dict comprehension is the recommended method. Since you are making a new list for each element, with [0, 0], they stay independent.

0
Barmar On

You're only making one copy of the list, and using this for every value in the dictionary. Function arguments are only evaluated once, and the result is passed to the function. So when you write

d = dict.fromkeys(keys, copy.deepcopy(l))

it's effectively equivalent to

temp = copy.deepcopy(l)
d = dict.fromkeys(keys, temp)

Use a dictionary comprehension instead, so you make a new copy for each element.

d = {key: copy.deepcopy(l) for key in keys}
0
alec_djinn On

I would make the dictionary in a different way. There isn't really the need for copy.deepcopy here.

keys = list(range(5))
values = [[0,0] for _ in keys]
d = dict(zip(keys, values))

#one-liner: d = {k:[0,0] for k in range(5)}

d[0][0] += 1
print(d) #{0: [1, 0], 1: [0, 0], 2: [0, 0], 3: [0, 0], 4: [0, 0]}