Создает ли python3 dict.copy только мелкие копии?

После прочтения в нескольких местах, в том числе здесь: Понимание 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}

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


person Stephen Gonzalez    schedule 27.06.2018    source источник
comment
Попробуйте то же самое, когда значения dict являются списками, и вы добавляете к ним вместо переназначения ссылки. На самом деле вы видите разницу между изменяемыми и неизменяемыми значениями.   -  person Patrick Haugh    schedule 27.06.2018
comment
Это поверхностная копия. Глубокая копия будет означать, что если a['one'] = [1,2,3] и вы изменили a['one'][0] = 5, то b['one'] не пострадает.   -  person chepner    schedule 27.06.2018
comment
@Patrick Haugh О, я понимаю эту часть, поскольку вы фактически будете хранить объекты в объектах, но если значения просто неизменяемы, то это, по сути, просто копия по значению?   -  person Stephen Gonzalez    schedule 27.06.2018
comment
@chepner, но примеры показывают, что в данной ситуации это не так, поэтому мне любопытно, относится ли это только к изменяемым значениям.   -  person Stephen Gonzalez    schedule 27.06.2018
comment
В этом случае нет разницы между поверхностной и глубокой копией. Только когда корневой объект содержит вложенные словари, списки или другие структуры данных, которые могут быть изменены (или содержат изменяемые объекты), существует разница между глубоким и поверхностным.   -  person Håken Lid    schedule 27.06.2018
comment
@StephenGonzalez Нет, это не так. В вашем примере вы делаете ссылку a['one'] на совершенно другой объект. В моем примере я изменяю текущий объект, на который ссылается a['one'].   -  person chepner    schedule 27.06.2018
comment
@HåkenLid ааа, попался. Спасибо за это!   -  person Stephen Gonzalez    schedule 27.06.2018
comment
@chepner Ваш пример был чем-то другим. Предполагалось, что я использую изменяемые типы, которые я понимаю, что происходит в этом сценарии. Я имел в виду неизменяемые типы, которые другие подтвердили, что мелкая и глубокая копия здесь не играют роли.   -  person Stephen Gonzalez    schedule 27.06.2018


Ответы (4)


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

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}

Вот он, вживую

person Netwave    schedule 27.06.2018
comment
Ах, спасибо, что подтвердили это. Так что, по сути, это не проблема, пока вы не начнете обращаться к изменяемым значениям или элементам, и именно тогда копирование против глубокого копирования действительно пригодится, верно? - person Stephen Gonzalez; 27.06.2018
comment
@StephenGonzalez, да, обычно так - person Netwave; 27.06.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) также создаст новый словарь, но также создаст новые объекты для заполнения этого словаря, а не ссылки на исходные.

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

person jacg    schedule 27.06.2018

пожалуйста, рассмотрите эту ситуацию объясненной, поэтому вы сможете легко понять метод ссылки и копирования().

    dic = {'data1': 100, 'data2': -54, 'data3': 247}
    dict1 = dic
    dict2 = dic.copy()
    print(dict2 is dic)
    # False
    print(dict1 is dic)
    # true

Первый оператор печати печатает false, потому что dict2 и dic являются двумя отдельными словарями с отдельными пространствами памяти, даже если они имеют одинаковое содержимое. Это происходит, когда мы используем функцию копирования. во-вторых, при назначении dic для dict1 не создается отдельный словарь с отдельными пространствами памяти, вместо этого dict1 ссылается на dic.

person Adithye T S    schedule 12.01.2021

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

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

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

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

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

person Olivier Melançon    schedule 27.06.2018