То, что вы наблюдаете, вообще не имеет ничего общего со словарями. Вас смущает разница между связыванием и мутацией.
Давайте сначала забудем о словарях и продемонстрируем проблему с помощью простых переменных. Как только мы поймем основную мысль, мы сможем вернуться к примеру со словарем.
a = 1
b = a
a = 2
print(b) # prints 1
- В первой строке вы создаете привязку между именем
a
и объектом 1
.
- Во второй строке вы создаете привязку между именем
b
и значением выражения a
..., которое является тем же самым объектом 1
, который был привязан к имени a
в предыдущей строке.
- В третьей строке вы создаете связь между именем
a
и объектом 2
, при этом забывая, что когда-либо была связь между a
и 1
.
Важно отметить, что этот последний шаг никоим образом не может повлиять на b
!
Ситуация полностью симметрична, поэтому, если бы строка 3 была b = 2
, это абсолютно не повлияло бы на a
.
Теперь люди часто ошибочно утверждают, что это каким-то образом является результатом неизменности целых чисел. Целые числа являются неизменяемыми в Python, но это совершенно не имеет значения. Если мы проделаем нечто подобное с некоторыми изменяемыми объектами, скажем, списками, то получим эквивалентные результаты.
a = [1]
b = a
a = [2]
print(b) # prints [1]
Снова
a
привязан к какому-то объекту
b
привязан к тому же объекту
a
теперь перенаправляется на какой-то другой объект
Это не может каким-либо образом повлиять на b
или объект, к которому он привязан [*]! Нигде не было предпринято никаких попыток мутировать какой-либо объект, поэтому изменчивость совершенно не имеет отношения к этой ситуации.
[*] на самом деле он изменяет счетчик ссылок на объект (по крайней мере, в CPython), но на самом деле это не наблюдаемое свойство объекта.
Однако если вместо перепривязки a
мы
- Используйте
a
для доступа к объекту, к которому он привязан.
- Мутировать этот объект
то мы повлияем на b
, потому что объект, к которому привязан b
, будет видоизменен:
a = [1]
b = a
a[0] = 2
print(b) # prints [2]
В общем, вы должны понять
Разница между связыванием и мутацией. Первый влияет на переменную (или, в более общем случае, на местоположение), а второй — на объект. В этом ключевое отличие
Повторная привязка имени (или местоположения в целом) не может повлиять на объект, к которому это имя было ранее привязано (помимо изменения счетчика ссылок).
Теперь в вашем примере вы создаете что-то, что выглядит (концептуально) так:
a ---> { 'three' ----------------------> 3
'two' -------------> 2 ^
'one' ---> 1 } ^ |
^ | |
| | |
b ---> { 'one' ----- | |
'two' --------------- |
'three' -------------------------
а затем a['one'] = 5
просто перепривязывает местоположение a['one']
так, что оно больше не привязано к 1
, а к 5
. Другими словами, эта стрелка, выходящая из первого 'one'
, теперь указывает куда-то еще.
Важно помнить, что это не имеет абсолютно никакого отношения к неизменности целых чисел. Если вы сделаете каждое целое число в своем примере изменяемым (например, заменив его списком, который его содержит: т. е. замените каждое вхождение 1
на [1]
(и аналогично для 2
и 3
)), то вы все равно будете наблюдать по существу такое же поведение : a['one'] = [1]
не повлияет на значение b['one']
.
Теперь, в этом последнем примере, где значения, хранящиеся в вашем словаре, представляют собой списки и, следовательно, структурированы, становится возможным различать поверхностное и глубокое копирование:
b = a
вообще не будет копировать словарь: он просто создаст b
новую привязку к тому же единственному словарю.
b = copy.copy(b)
создаст новый словарь с внутренними привязками к тем же спискам. Словарь копируется, но на его содержимое (ниже верхнего уровня) просто ссылается новый словарь.
b = copy.deepcopy(a)
также создаст новый словарь, но также создаст новые объекты для заполнения этого словаря, а не ссылки на исходные.
Следовательно, если вы мутируете (а не перепривязываете) что-то в случае неглубокой копии, другой словарь "увидит" мутацию, потому что два словаря имеют общие объекты. В глубокой копии этого не происходит.
person
jacg
schedule
27.06.2018
a['one'] = [1,2,3]
и вы изменилиa['one'][0] = 5
, тоb['one']
не пострадает. - person chepner   schedule 27.06.2018a['one']
на совершенно другой объект. В моем примере я изменяю текущий объект, на который ссылаетсяa['one']
. - person chepner   schedule 27.06.2018