7

I don't understand these cases:

content = {'a': {'v': 1}, 'b': {'v': 2}}
d1 = {'k1': {}}
d2 = {'k2': {}}
d1['k1'].update(content)
print(d1)
content['a']['v'] = 3
content['b']['v'] = 4
d2['k2'].update(content)
print(d2)
print(d1)
>>> {'k1': {'a': {'v': 1}, 'b': {'v': 2}}}
>>> {'k2': {'a': {'v': 3}, 'b': {'v': 4}}}
>>> {'k1': {'a': {'v': 3}, 'b': {'v': 4}}}

In the case above the content of d1 is changed after the variable content is updated.

content = {'a': 1, 'b': 2}
d1 = {'k1': {}}
d2 = {'k2': {}}
d1['k1'].update(content)
print(d1)
content['a'] = 3
content['b'] = 4
d2['k2'].update(content)
print(d2)
print(d1)
>>> {'k1': {'a': 1, 'b': 2}}
>>> {'k2': {'a': 3, 'b': 4}}
>>> {'k1': {'a': 1, 'b': 2}} 

However in this case d1 is not altered even if the variable content was changed. I don't understand why... any idea?

smci
  • 32,567
  • 20
  • 113
  • 146
Marc
  • 1,340
  • 2
  • 14
  • 23
  • 2
    I assume all these upvotes but no comments/answers are because people also think this is strange behavior. :P – Mateen Ulhaq May 21 '18 at 09:29
  • 3
    @MateenUlhaq It's not, it has to to with the fact that `dict` is mutable object and `int` is immutable. This makes python handle them differently. Similar thing would happen if content was a `list` (mutable). – zipa May 21 '18 at 09:35
  • Isn't the second example mutating a dictionary (`content`) as well? – Mateen Ulhaq May 21 '18 at 09:36
  • 2
    @MateenUlhaq No, in first example keys point to `dict` and in second they point to an `int`. What makes this confusing is the nested nature of these dictionaries but nonetheless, in the background, everything consists from a bunch of pointers that get resolved once called. – zipa May 21 '18 at 09:41
  • @MateenUlhaq If I were good at explaining this I'd post an answer :) – zipa May 21 '18 at 09:42
  • https://stackoverflow.com/questions/23852480/assigning-value-in-python-dict-copy-vs-reference – CristiFati May 21 '18 at 09:45

3 Answers3

7

see shallow vs deep copy.

The copy here is a shallow copy so the first level entries are copies but the nested structures are references.

  • A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
  • A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the
    original.
perreal
  • 94,503
  • 21
  • 155
  • 181
2

The key difference between your two snippets is that content['a']['v'] = 3 is a completely different operation than content['a'] = 3. In the first case, you're modifying the inner dictionary by changing its v key. In the latter case, you're replacing the value in the dictionary without modifying it.

It's confusing when everything's a dictionary, so let's replace the dictionaries with variables and instances of a class:

class Person:
    def __init__(self, name):
        self.name = name

# these two variables are represent your `content` dict
a = Person('Andy')  # this variable represents `{'v': 1}`
b = Person('Belle')  # this variable represents `{'v': 2}`

# the equivalent of `d1['k1'].update(content)` is a simple assignment
k1_a = a

# and the equivalent of `content['a']['v'] = 3` is changing a's name
a.name = 'Aaron'

# because k1_a and a are the same Person instance, this is reflected in k1_a:
print(k1_a.name)  # output: Aaron

The key points to note here are that

  1. k1_a = a doesn't make a copy of the Person; similar to how d1['k1'].update(content) doesn't make a copy of the {'v': 1} dict.
  2. a.name = 'Aaron' modifies the Person; similar to how content['a']['v'] = 3 modifies the inner dict.

The equivalent of your 2nd snippet looks like this:

a = 'Andy'
b = 'Belle'

k1_a = a

a = 'Aaron'

print(k1_a)  # output: Andy

This time, no object is ever modified. All we're doing is overwriting the value of the a variable, exactly how content['a'] = 3 overwrites the value of the a key in your dict.


If you don't want the changes in the inner dicts to be reflected in other dicts, you have to copy them with copy.deepcopy:

import copy

content = {'a': {'v': 1}, 'b': {'v': 2}}
d1 = {'k1': {}}
d2 = {'k2': {}}
d1['k1'].update(copy.deepcopy(content))
print(d1)
content['a']['v'] = 3
content['b']['v'] = 4
d2['k2'].update(copy.deepcopy(content))
print(d2)
print(d1)

# output:
# {'k1': {'a': {'v': 1}, 'b': {'v': 2}}}
# {'k2': {'a': {'v': 3}, 'b': {'v': 4}}}
# {'k1': {'a': {'v': 1}, 'b': {'v': 2}}}
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
0

If we replace the update() with a simple assignment:

# d1['k1'].update(content)
d1['k1'] = content

We get:

{'k1': {'a': 1, 'b': 2}}
{'k2': {'a': 3, 'b': 4}}
{'k1': {'a': 3, 'b': 4}}

(Which is different from what update does in your example.) This is because update accepts an iterable (e.g. a dictionary) and copies the key value pairs inside. It's equivalent to doing:

d1['k1'] = {k: v for k, v in content.items()}

And of course, the int values are immutables and so their reassignment does not affect the original.

Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135