5

Почему обновление "поверхностной" копии словаря не обновляет "оригинальный" словарь?

78

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

Здравствуйте! Я изучаю документацию по методу dict.copy() в Python, который, как указано, создает поверхностную копию словаря. Также это подтверждается в книге, которую я читаю (Python Reference от Биза), где говорится:

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

Вот пример:

original = dict(a=1, b=2)
new = original.copy()
new.update({'c': 3})
print(original)  # Вывод: {'a': 1, 'b': 2}
print(new)       # Вывод: {'a': 1, 'c': 3, 'b': 2}

Я предполагал, что обновление значения в new (добавление 'c': 3) также обновит значение в original, так как это поверхностная копия. Для сравнения, вот пример работы со списками:

original = [1, 2, 3]
new = original
new.append(4)
print(new)      # Вывод: [1, 2, 3, 4]
print(original) # Вывод: [1, 2, 3, 4]

В этом случае изменения в new отражаются на original, как и ожидалось.

Так что теперь я задаюсь вопросом: почему метод dict.copy() не работает так, как я ожидал? Возможно, я неправильно понимаю различия между поверхностным и глубоким копированием? Буду благодарен за любые объяснения!

5 ответ(ов)

0

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

Метод .copy() создает поверхностную копию объекта, в то время как оператор = просто создаёт ссылку на оригинальный объект. Это значит, что если вы используете new = original, то изменения в объекте new также отразятся на original, так как оба будут ссылаться на один и тот же объект. Чтобы избежать этого, и создать независимую копию, следует воспользоваться методом .copy().

0

При работе с копиями словарей в Python важно понимать разницу между поверхностным и глубоким копированием. Рассмотрим следующий пример:

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

На данном этапе мы создали копию словаря original в переменной new. Это - поверхностное копирование, и это значит, что все изменения на верхнем уровне new не отразятся на original. Давайте изменим значение на первом уровне:

new['a'] = 10
# new = {'a': 10, 'b': 2, 'c': {'d': 4, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 4, 'e': 5}}
# в original нет изменений, так как ['a'] - это неизменяемый целочисленный тип

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

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'] указывает на тот же изменяемый словарь, что и original['c'], поэтому изменится и оригинал

Таким образом, если вы копируете словарь с изменяемыми объектами (как, например, вложенные словари), любые изменения, сделанные в этих вложенных объектах через копию, отразятся на оригинале. Чтобы избежать этого, можно использовать copy.deepcopy() для создания полной копии, включая вложенные объекты.

0

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

Вот здесь:

>>> new = original

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

В то время как здесь:

>>> new = original.copy()
>>> # или
>>> new = list(original) # dict(original)

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

0

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

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

Хэш (идентификатор) значения parent[1] и parent_copy[1] идентичны, что указывает на то, что [1, 2, 3] в parent[1] и parent_copy[1] хранятся по одному и тому же адресу памяти.

Однако, хэши parent и parent_copy различны, что говорит о том, что это разные словари, и parent_copy — это новый словарь, который имеет ссылки на значения из parent.

0

"new" и "original" являются разными словарями, поэтому вы можете обновить только один из них. Элементы словаря копируются поверхностно, а не сам словарь.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь