544

While reading up the documentation for dict.copy(), it says that it makes a shallow copy of the dictionary. Same goes for the book I am following (Beazley's Python Reference), which says:

The m.copy() method makes a shallow copy of the items contained in a mapping object and places them in a new mapping object.

Consider this:

>>> original = dict(a=1, b=2)
>>> new = original.copy()
>>> new.update({'c': 3})
>>> original
{'a': 1, 'b': 2}
>>> new
{'a': 1, 'c': 3, 'b': 2}

So I assumed this would update the value of original (and add 'c': 3) also since I was doing a shallow copy. Like if you do it for a list:

>>> original = [1, 2, 3]
>>> new = original
>>> new.append(4)
>>> new, original
([1, 2, 3, 4], [1, 2, 3, 4])

This works as expected.

Since both are shallow copies, why is that the dict.copy() doesn't work as I expect it to? Or my understanding of shallow vs deep copying is flawed?

ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
user225312
  • 126,773
  • 69
  • 172
  • 181
  • 6
    Quaint that they don't explain "shallow". Insider knowledge, wink. Just the dict and keys are a copy while nested dicts inside that first level are references, cannot be deleted in a loop for example. Thus Python's dict.copy() in that case is neither useful nor intuitive. Thanks for your question. – gseattle Jun 09 '17 at 10:34
  • "Since both are shallow copies" - that's the issue here. The second example **is not** a copy, shallow or otherwise. – Karl Knechtel Mar 26 '23 at 22:02

7 Answers7

1265

By "shallow copying" it means the content of the dictionary is not copied by value, but just creating a new reference.

>>> a = {1: [1,2,3]}
>>> b = a.copy()
>>> a, b
({1: [1, 2, 3]}, {1: [1, 2, 3]})
>>> a[1].append(4)
>>> a, b
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})

In contrast, a deep copy will copy all contents by value.

>>> import copy
>>> c = copy.deepcopy(a)
>>> a, c
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
>>> a[1].append(5)
>>> a, c
({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})

So:

  1. b = a: Reference assignment, Make a and b points to the same object.

    Illustration of 'a = b': 'a' and 'b' both point to '{1: L}', 'L' points to '[1, 2, 3]'.

  2. b = a.copy(): Shallow copying, a and b will become two isolated objects, but their contents still share the same reference

    Illustration of 'b = a.copy()': 'a' points to '{1: L}', 'b' points to '{1: M}', 'L' and 'M' both point to '[1, 2, 3]'.

  3. b = copy.deepcopy(a): Deep copying, a and b's structure and content become completely isolated.

    Illustration of 'b = copy.deepcopy(a)': 'a' points to '{1: L}', 'L' points to '[1, 2, 3]'; 'b' points to '{1: M}', 'M' points to a different instance of '[1, 2, 3]'.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • 1
    Nice answer, but you might consider correcting the grammatical error in your first sentence. And there's no reason to not use `L` again in `b`. Doing so would simplify the example. – Tom Russell Oct 27 '17 at 04:47
  • @kennytm: What is the difference between the first two examples, in fact? You get there the same result, but slightly different inner implementation, but for what does it matter? – JavaSa Jan 20 '18 at 11:53
  • @TomRussell: Or anybody, since this question is quite old, my clarification question is for everybody – JavaSa Jan 20 '18 at 11:58
  • @JavaSa It matters if, say, you do `b[1][0] = 5`. If `b` is a shallow copy, you've just changed `a[1][0]`. – Tom Russell Jan 20 '18 at 21:11
  • @TomRussell: I meant first two examples from drawings, not text, my mistake, so question is still valid – JavaSa Jan 21 '18 at 14:56
  • 1
    @JavaSa `a[1] = {}` will affect `b` in the first example, and won't affect `b` in the second example. – kennytm Jan 22 '18 at 18:08
  • @kennytm `a[1] = {}` *doesn't* affect `b` in the first example. I leave it to you to explain how this is. :-) – Tom Russell Jan 24 '18 at 08:33
  • @TomRussell Just to clarify, "first example" means the first example in the picture (i.e. `b = a` instead of `b = a.copy()`). – kennytm Jan 24 '18 at 09:49
  • @kennytm Ah, bummer. You anticipated my objection exactly. IIRC, I thought we going to get to have an interesting discussion about the semantics of copying a list object vs the items in it. :-( – Tom Russell Feb 09 '18 at 08:09
  • 3
    Great explanation,... really saved my day! Thanks...Can this same be applied tp the list, str, and other datatypes of python? – Bhuro Feb 19 '18 at 13:38
  • @Bhuro Yes to `list`, no to `str` (a Python string is immutable so this doesn't matter). – kennytm Feb 20 '18 at 19:06
  • a=[[[1,2,3]]],id(a[0][0]) != id(copy.deepcopy(a)[0][0]) – Voyager Mar 19 '19 at 15:23
  • Plus one for nice visualization. – Petr Vepřek Jul 03 '20 at 11:49
  • @kennytm : `dic_a = {'A': 'Apple', 'B': 'Ball'}` and `dic_b = dic_a`, then `dic_b['A'] = 'Adam'` changes the value in both dictionaries. But when `dic_b = dic_a.copy()`, then doing `dic_b['A'] = 'Adam'` changes the value of `A` only in `dic_b`. Why does the value change in the first case when strings are immutable? – Sheldore Sep 20 '20 at 12:03
  • 1
    @Sheldore `dic_a` and `dic_b` are referencing the same dictionary when you `dic_b = dic_a`. The strings in the dictionary are immutable but that's irrelevant, because `dic_b['A'] = 'Adam'` mutates the dictionary itself. – kennytm Sep 20 '20 at 17:54
50

Take this example:

original = dict(a=1, b=2, c=dict(d=4, e=5))
new = original.copy()

Now let's change a value in the 'shallow' (first) level:

new['a'] = 10
# new = {'a': 10, 'b': 2, 'c': {'d': 4, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 4, 'e': 5}}
# no change in original, since ['a'] is an immutable integer

Now let's change a value one level deeper:

new['c']['d'] = 40
# new = {'a': 10, 'b': 2, 'c': {'d': 40, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 40, 'e': 5}}
# new['c'] points to the same original['d'] mutable dictionary, so it will be changed
eumiro
  • 207,213
  • 34
  • 299
  • 261
  • 19
    `no change in original, since ['a'] is an immutable integer` This. It actually answers the question asked. – CivFan Apr 14 '15 at 18:08
  • The fact that integers are immutable is not the reason the original is unchanged. You could observe the same behaviour when using mutable objects instead of 1 and 10 as the values. – julaine Jul 25 '23 at 13:41
42

It's not a matter of deep copy or shallow copy, none of what you're doing is deep copy.

Here:

>>> new = original 

you're creating a new reference to the the list/dict referenced by original.

while here:

>>> new = original.copy()
>>> # or
>>> new = list(original) # dict(original)

you're creating a new list/dict which is filled with a copy of the references of objects contained in the original container.

Lie Ryan
  • 62,238
  • 13
  • 100
  • 144
  • True, as far as it goes... but it doesn't go far enough to answer the OP question about shallow vs. deep. – MarkHu Feb 11 '21 at 02:02
12

Adding to kennytm's answer. When you do a shallow copy parent.copy() a new dictionary is created with same keys,but the values are not copied they are referenced.If you add a new value to parent_copy it won't effect parent because parent_copy is a new dictionary not reference.

parent = {1: [1,2,3]}
parent_copy = parent.copy()
parent_reference = parent

print id(parent),id(parent_copy),id(parent_reference)
#140690938288400 140690938290536 140690938288400

print id(parent[1]),id(parent_copy[1]),id(parent_reference[1])
#140690938137128 140690938137128 140690938137128

parent_copy[1].append(4)
parent_copy[2] = ['new']

print parent, parent_copy, parent_reference
#{1: [1, 2, 3, 4]} {1: [1, 2, 3, 4], 2: ['new']} {1: [1, 2, 3, 4]}

The hash(id) value of parent[1], parent_copy[1] are identical which implies [1,2,3] of parent[1] and parent_copy[1] stored at id 140690938288400.

But hash of parent and parent_copy are different which implies They are different dictionaries and parent_copy is a new dictionary having values reference to values of parent

Vkreddy
  • 1,618
  • 1
  • 16
  • 13
5

"new" and "original" are different dicts, that's why you can update just one of them.. The items are shallow-copied, not the dict itself.

Joril
  • 19,961
  • 13
  • 71
  • 88
3

In your second part, you should use new = original.copy()

.copy and = are different things.

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
朱骏杰
  • 31
  • 1
2

Contents are shallow copied.

So if the original dict contains a list or another dictionary, modifying one them in the original or its shallow copy will modify them (the list or the dict) in the other.

Jungle Hunter
  • 7,233
  • 11
  • 42
  • 67