Понимание dict.copy () - мелкое или глубокое?

429

Читая документацию для dict.copy(), он говорит, что делает мелкую копию словаря. То же самое касается книги, которой я следую (Справочник Бизли по Python), в которой говорится:

Метод m.copy () создает поверхностную копию элементов, содержащихся в объекте отображения, и помещает их в новый объект отображения.

Учти это:

>>> original = dict(a=1, b=2)
>>> new = original.copy()
>>> new.update({'c': 3})
>>> original
{'a': 1, 'b': 2}
>>> new
{'a': 1, 'c': 3, 'b': 2}

Поэтому я предположил, что это обновит значение original(и добавит 'c': 3) также, так как я делал мелкую копию. Например, если вы делаете это для списка:

>>> original = [1, 2, 3]
>>> new = original
>>> new.append(4)
>>> new, original
([1, 2, 3, 4], [1, 2, 3, 4])

Это работает как ожидалось.

Поскольку оба являются мелкими копиями, почему dict.copy()это не работает, как я ожидаю? Или мое понимание мелкого и глубокого копирования ошибочно?

user225312
источник
2
Странно, что они не объясняют "мелкой". Инсайдерские знания, подмигивание. Только dict и ключи являются копией, в то время как вложенные указания внутри этого первого уровня являются ссылками, например, их нельзя удалить в цикле. Таким образом, Python dict.copy () в этом случае не является ни полезным, ни интуитивным. Спасибо за ваш вопрос.
gseattle

Ответы:

992

Под "мелким копированием" подразумевается, что содержимое словаря не копируется по значению, а просто создает новую ссылку.

>>> a = {1: [1,2,3]}
>>> b = a.copy()
>>> a, b
({1: [1, 2, 3]}, {1: [1, 2, 3]})
>>> a[1].append(4)
>>> a, b
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})

Напротив, глубокая копия будет копировать все содержимое по значению.

>>> import copy
>>> c = copy.deepcopy(a)
>>> a, c
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
>>> a[1].append(5)
>>> a, c
({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})

Так:

  1. b = a: Ссылка на назначение, Марка aи bуказывает на один и тот же объект.

    Иллюстрация «a = b»: «a» и «b» оба указывают на «{1: L}», «L» указывает на «[1, 2, 3]».

  2. b = a.copy(): Мелкое копирование, aи bон станет двумя изолированными объектами, но их содержимое все еще будет иметь одну и ту же ссылку

    Иллюстрация «b = a.copy ()»: «a» указывает на «{1: L}», «b» указывает на «{1: M}», «L» и «M» указывают на «[ 1, 2, 3].

  3. b = copy.deepcopy(a)Глубокое копирование, aа также bструктура и содержание становятся полностью изолированными.

    Иллюстрация «b = copy.deepcopy (a)»: «a» указывает на «{1: L}», «L» указывает на «[1, 2, 3]»;  «b» указывает на «{1: M}», «M» указывает на другой экземпляр «[1, 2, 3]».

kennytm
источник
Хороший ответ, но вы можете исправить грамматическую ошибку в первом предложении. И нет никаких причин, чтобы не использовать Lснова в b. Это упростит пример.
Том Рассел
@kennytm: Какая разница между первыми двумя примерами? Вы получаете тот же результат, но немного отличную внутреннюю реализацию, но для чего это важно?
JavaSa
@ TomRussell: Или кто-нибудь, так как этот вопрос довольно старый, мой
уточняющий
@JavaSa Это имеет значение, если, скажем, вы делаете b[1][0] = 5. Если bэто мелкая копия, вы только что изменились a[1][0].
Том Рассел
2
Отличное объяснение, ... действительно спас мой день! Спасибо ... Можно ли это применить к списку, str и другим типам данных python?
Bhuro
38

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

Вот:

>>> new = original 

вы создаете новую ссылку на список / dict, на который ссылается оригинал.

пока здесь:

>>> new = original.copy()
>>> # or
>>> new = list(original) # dict(original)

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

Ли Райан
источник
31

Возьмите этот пример:

original = dict(a=1, b=2, c=dict(d=4, e=5))
new = original.copy()

Теперь давайте изменим значение на «мелком» (первом) уровне:

new['a'] = 10
# new = {'a': 10, 'b': 2, 'c': {'d': 4, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 4, 'e': 5}}
# no change in original, since ['a'] is an immutable integer

Теперь давайте изменим значение на один уровень глубже:

new['c']['d'] = 40
# new = {'a': 10, 'b': 2, 'c': {'d': 40, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 40, 'e': 5}}
# new['c'] points to the same original['d'] mutable dictionary, so it will be changed
eumiro
источник
8
no change in original, since ['a'] is an immutable integerЭта. Это на самом деле отвечает на заданный вопрос.
CivFan
8

Добавление к ответу Кеннимма. Когда вы выполняете поверхностное копирование parent.copy (), создается новый словарь с теми же ключами, но значения не копируются, на них ссылаются. Если вы добавите новое значение в parent_copy, это не повлияет на parent, потому что parent_copy - новый словарь не ссылка.

parent = {1: [1,2,3]}
parent_copy = parent.copy()
parent_reference = parent

print id(parent),id(parent_copy),id(parent_reference)
#140690938288400 140690938290536 140690938288400

print id(parent[1]),id(parent_copy[1]),id(parent_reference[1])
#140690938137128 140690938137128 140690938137128

parent_copy[1].append(4)
parent_copy[2] = ['new']

print parent, parent_copy, parent_reference
#{1: [1, 2, 3, 4]} {1: [1, 2, 3, 4], 2: ['new']} {1: [1, 2, 3, 4]}

Значение hash (id) parent [1] , parent_copy [1] идентично, что подразумевает [1,2,3] parent [1] и parent_copy [1], хранящиеся с идентификатором 140690938288400.

Но хэш parent и parent_copy различаются, что подразумевает, что это разные словари, а parent_copy - это новый словарь, имеющий значения, ссылающиеся на значения parent

Вкредди Коматиредды
источник
5

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

Joril
источник
2

Содержание мелко скопировано.

Таким образом, если оригинал dictсодержит один listили другой dictionary, изменение одного из них в оригинале или его мелкой копии изменит их ( listили или dict) в другом.

Охотник джунглей
источник
1

Во второй части вы должны использовать new = original.copy()

.copyи =разные вещи.

朱骏 杰
источник