When making a copy of a dictionary in Python, most approaches make a shallow copy and the original dictionary is changed by accident. Use copy.deepcopy()
if you need a recursive copy of all elements.
It’s a common Python pitfall for beginners to forget that objects are mutable when copying them and to make unintended changes as a consequence. I thought I had it covered until this happend:
### Don't do this ###
a = {'a_1':[1], "a_2":[2]}
b = {'b_1':[1], "b_2":[2]}
c = {'a':a,'b':b}
def f(x):
x = x.copy() # Nice try, doesn't work
x['a']['a_1'] = "test"
return x
print(f(c))
print(c)
{'a': {'a_1': 'test', 'a_2': [2]}, 'b': {'b_1': [1], 'b_2': [2]}}
{'a': {'a_1': 'test', 'a_2': [2]}, 'b': {'b_1': [1], 'b_2': [2]}}
The function had an unintended side-effect even though I was purposefully using .copy()
!
To be clear, this is the typical example in Python beginners courses:
a = [1,2,3]
b = a
b.append(1)
print(a)
print(b)
[1, 2, 3, 1]
[1, 2, 3, 1]
An easy solution it to copy element-by-element or use .copy()
a = [1,2,3]
b = a[:] # or b = a.copy()
b.append(1)
print(a)
print(b)
[1, 2, 3]
[1, 2, 3, 1]
As I learned the hard way, the solution is not that simple for dictionaries!
As above, we’d expect just assigning a dictionary to a new variable to fail. And it does, both when we change elements of the dictionary and when we change the values of the dictionary.
We want c
to not change because of the two changes to d
: one to a key of d
, the other to a value of d
.
a = {'a_1':[1], "a_2":[2]}
b = {'b_1':[1], "b_2":[2]}
c = {'a':a,'b':b}
d = c
# Assign new key
d['new'] = d.pop('b')
print(f"{d=}")
# Assign new value to an element within the dictionary
d['a']['a_1'] = 'test'
print(f"{d=}")
print(f"{c=}")
d={'a': {'a_1': [1], 'a_2': [2]}, 'new': {'b_1': [1], 'b_2': [2]}}
d={'a': {'a_1': 'test', 'a_2': [2]}, 'new': {'b_1': [1], 'b_2': [2]}}
c={'a': {'a_1': 'test', 'a_2': [2]}, 'new': {'b_1': [1], 'b_2': [2]}}
[!] This copies the dictionary structure, but does not copy the elements themselves!
See how the new key isn’t present in c
but the change to the object in a
is.
a = {'a_1':[1], "a_2":[2]}
b = {'b_1':[1], "b_2":[2]}
c = {'a':a,'b':b}
d = {key:item for key, item in c.items()}
# Assign new key
d['new'] = d.pop('b')
print(f"{d=}")
# Assign new value to an element within the dictionary
d['a']['a_1'] = 'test'
print(f"{d=}")
print(f"{c=}")
{'a': {'a_1': [1], 'a_2': [2]}, 'new': {'b_1': [1], 'b_2': [2]}}
{'a': {'a_1': 'test', 'a_2': [2]}, 'new': {'b_1': [1], 'b_2': [2]}}
{'a': {'a_1': 'test', 'a_2': [2]}, 'b': {'b_1': [1], 'b_2': [2]}}
[!] This copies the dictionary structure, but does not copy the elements themselves!
a = {'a_1':[1], "a_2":[2]}
b = {'b_1':[1], "b_2":[2]}
c = {'a':a,'b':b}
d = c.copy()
# Assign new key
d['new'] = d.pop('b')
print(f"{d=}")
# Assign new value to an element within the dictionary
d['a']['a_1'] = 'test'
print(f"{d=}")
print(f"{c=}")
{'a': {'a_1': 'test1', 'a_2': [2]}, 'b': {'b_1': [1], 'b_2': [2]}}
{'a': {'a_1': 'test1', 'a_2': [2]}, 'new': {'b_1': [1], 'b_2': [2]}}
copy.deepcopy()
copies the structure and elements recursively. c
is unchanged after the changes to d
import copy
a = {'a_1':[1], "a_2":[2]}
b = {'b_1':[1], "b_2":[2]}
c = {'a':a,'b':b}
d = copy.deepcopy(c)
# Assign new key
d['new'] = d.pop('b')
print(f"{d=}")
# Assign new value to an element within the dictionary
d['a']['a_1'] = 'test'
print(f"{d=}")
print(f"{c=}")
d={'a': {'a_1': [1], 'a_2': [2]}, 'new': {'b_1': [1], 'b_2': [2]}}
d={'a': {'a_1': 'test', 'a_2': [2]}, 'new': {'b_1': [1], 'b_2': [2]}}
c={'a': {'a_1': [1], 'a_2': [2]}, 'b': {'b_1': [1], 'b_2': [2]}}
Be very careful when changing mutable objects within dictionaries and use copy.deepcopy()
when necessary. Be aware that deepcopy is slow.