Python3 dict.copy все еще создает только полные копии? - PullRequest
0 голосов
/ 27 июня 2018

После прочтения в нескольких местах, в том числе здесь: Понимание dict.copy () - мелкое или глубокое?

Он утверждает, что dict.copy создаст поверхностную копию, иначе известную как ссылка на те же значения. Однако, когда я сам играю с ним в python3 repl, я получаю копию только по значению?

a = {'one': 1, 'two': 2, 'three': 3}
b = a.copy()

print(a is b) # False
print(a == b) # True

a['one'] = 5
print(a) # {'one': 5, 'two': 2, 'three': 3}
print(b) # {'one': 1, 'two': 2, 'three': 3}

Означает ли это, что мелкие и глубокие копии не обязательно влияют на неизменяемые значения?

Ответы [ 3 ]

0 голосов
/ 27 июня 2018

A мелкая копия некоторого контейнера означает, что возвращается новый идентичный объект, но его значения являются теми же объектами.

Это означает, что изменение значений копии приведет к изменению значений оригинала. В вашем примере вы не изменяете значение, а обновляете ключ.

Вот пример мутации значения.

d = {'a': []}

d_copy = d.copy()

print(d is d_copy) # False
print(d['a'] is d['a']) # True

d['a'].append(1)
print(d_copy) # {'a': [1]}

С другой стороны, глубокая копия контейнера возвращает новый идентичный объект, но там, где значения также были рекурсивно скопированы.

0 голосов
/ 27 июня 2018

То, что вы наблюдаете, не имеет никакого отношения к словарям вообще. Вас смущает разница между связыванием и мутацией .

Давайте сначала забудем словари и продемонстрируем проблему с помощью простых переменных. Как только мы поймем фундаментальный момент, мы сможем вернуться к примеру со словарем.

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 мы

  1. Используйте a для доступа к объекту, к которому он привязан
  2. Мутировать этот объект

тогда мы будем влиять на b, потому что объект, к которому привязан b, будет видоизменяться:

a = [1]
b = a
a[0] = 2
print(b)  # prints [2]

Таким образом, вы должны понимать,

  1. Разница между связыванием и мутацией . Первый влияет на переменную (или, в более общем случае, на местоположение), а второй влияет на объект. В этом заключается ключевое отличие

  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) также создаст новый словарь, но он также создаст новые объекты для заполнения этого словаря, вместо того, чтобы ссылаться на исходные.

Следовательно, если вы мутируете (а не перепривязываете ) что-то в случае мелкой копии, другой словарь "увидит" мутацию, потому что два словаря совместно используют объекты. Это не происходит в глубокой копии.

0 голосов
/ 27 июня 2018

Целые числа являются неизменными, проблема возникает при ссылке на объекты, проверьте этот похожий пример:

import copy
a = {'one': [], 'two': 2, 'three': 3}
b = a.copy()
c = copy.deepcopy(a)
print(a is b) # False
print(a == b) # True

a['one'].append(5)
print(a) # {'one': [5], 'two': 2, 'three': 3}
print(b) # {'one': [5], 'two': 2, 'three': 3}
print(c) # {'one': [], 'two': 2, 'three': 3}

Здесь у вас есть вживую

...