Почему обновление "поверхностной" копии словаря не обновляет "оригинальный" словарь?
Проблема с использованием 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 ответ(ов)
В вашей второй части вы должны использовать new = original.copy()
.
Метод .copy()
создает поверхностную копию объекта, в то время как оператор =
просто создаёт ссылку на оригинальный объект. Это значит, что если вы используете new = original
, то изменения в объекте new
также отразятся на original
, так как оба будут ссылаться на один и тот же объект. Чтобы избежать этого, и создать независимую копию, следует воспользоваться методом .copy()
.
При работе с копиями словарей в 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()
для создания полной копии, включая вложенные объекты.
Это не вопрос глубокого или поверхностного копирования, поскольку ни один из предложенных вами вариантов не является глубоким копированием.
Вот здесь:
>>> new = original
вы создаете новую ссылку на список/словарь, на который ссылается original
.
В то время как здесь:
>>> new = original.copy()
>>> # или
>>> new = list(original) # dict(original)
вы создаете новый список/словарь, который заполняется копиями ссылок на объекты, содержащиеся в оригинальном контейнере.
Дополняя ответ 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
.
"new" и "original" являются разными словарями, поэтому вы можете обновить только один из них. Элементы словаря копируются поверхностно, а не сам словарь.
Преобразование списка словарей в DataFrame pandas
Почему использовать dict.get(key) вместо dict[key]?
Как преобразовать вложенный словарь Python в объект?
Ошибка: "'dict' объект не имеет метода 'iteritems'"
Есть ли питоний способ объединить два словаря (сложив значения для ключей, которые присутствуют в обоих)?