Почему изменения во вложенном словаре внутри dict2 влияют на dict1?

Я не понимаю этих случаев:

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}}}

В приведенном выше случае содержимое d1 изменяется после обновления переменной content.

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}} 

Однако в этом случае d1 не изменяется, даже если была изменена переменная content. Я не понимаю, почему... есть идеи?


person Marc    schedule 21.05.2018    source источник
comment
Я предполагаю, что все эти голоса за, но без комментариев/ответов, потому что люди также думают, что это странное поведение. :П   -  person Mateen Ulhaq    schedule 21.05.2018
comment
@MateenUlhaq Это не так, это связано с тем фактом, что dict является изменяемым объектом, а int неизменяемым. Это заставляет python обрабатывать их по-разному. То же самое произошло бы, если бы контент был list (изменчивым).   -  person zipa    schedule 21.05.2018
comment
Разве второй пример также не изменяет словарь (content)?   -  person Mateen Ulhaq    schedule 21.05.2018
comment
@MateenUlhaq Нет, в первом примере ключи указывают на dict, а во втором — на int. Что сбивает с толку, так это вложенный характер этих словарей, но, тем не менее, в фоновом режиме все состоит из набора указателей, которые разрешаются после вызова.   -  person zipa    schedule 21.05.2018
comment
@MateenUlhaq Если бы я умел объяснять это, я бы опубликовал ответ :)   -  person zipa    schedule 21.05.2018
comment
stackoverflow.com/ вопросы/23852480/   -  person CristiFati    schedule 21.05.2018


Ответы (3)


см. мелкое и глубокое копирование.

Копия здесь является поверхностной копией, поэтому записи первого уровня являются копиями, а вложенные структуры являются ссылками.

  • Поверхностная копия создает новый составной объект, а затем (насколько это возможно) вставляет в него ссылки на объекты, найденные в оригинале.
  • Глубокая копия создает новый составной объект, а затем рекурсивно вставляет в него копии объектов, найденных в оригинале
    .
person perreal    schedule 21.05.2018
comment
Пожалуйста, не отвечайте на дубликаты. - person smci; 21.05.2018
comment
Я проголосовал за закрытие за то, что это стоит. - person perreal; 21.05.2018

Ключевое различие между вашими двумя фрагментами заключается в том, что content['a']['v'] = 3 — это совершенно другая операция, чем content['a'] = 3. В первом случае вы модифицируете внутренний словарь, изменяя его ключ v. В последнем случае вы заменяете значение в словаре, не изменяя его.

Это сбивает с толку, когда все является словарем, поэтому давайте заменим словари переменными и экземплярами класса:

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

Ключевые моменты, на которые следует обратить внимание, заключаются в том, что

  1. k1_a = a не делает копию Person; аналогично тому, как d1['k1'].update(content) не делает копию {'v': 1} dict.
  2. a.name = 'Aaron' изменяет человека; аналогично тому, как content['a']['v'] = 3 изменяет внутренний словарь.

Эквивалент вашего второго фрагмента выглядит так:

a = 'Andy'
b = 'Belle'

k1_a = a

a = 'Aaron'

print(k1_a)  # output: Andy

На этот раз ни один объект никогда не модифицируется. Все, что мы делаем, это перезаписываем значение переменной a, точно так же, как content['a'] = 3 перезаписывает значение ключа a в вашем словаре.


Если вы не хотите, чтобы изменения во внутренних словарях отражались в других словарях, вам нужно скопировать их с помощью 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}}}
person Aran-Fey    schedule 21.05.2018

Если мы заменим update() простым присвоением:

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

Мы получили:

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

(Это отличается от того, что делает update в вашем примере.) Это связано с тем, что update принимает итерируемый объект (например, словарь) и копирует пары ключ-значение внутри. Это эквивалентно выполнению:

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

И, конечно же, значения int являются неизменяемыми, поэтому их переназначение не влияет на исходное.

person Mateen Ulhaq    schedule 21.05.2018