То, что вы наблюдаете, не имеет никакого отношения к словарям вообще. Вас смущает разница между связыванием и мутацией .
Давайте сначала забудем словари и продемонстрируем проблему с помощью простых переменных. Как только мы поймем фундаментальный момент, мы сможем вернуться к примеру со словарем.
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)
также создаст новый словарь, но он также создаст новые объекты для заполнения этого словаря, вместо того, чтобы ссылаться на исходные.
Следовательно, если вы мутируете (а не перепривязываете ) что-то в случае мелкой копии, другой словарь "увидит" мутацию, потому что два словаря совместно используют объекты. Это не происходит в глубокой копии.