了解dict.copy()-浅还是深?


429

在阅读的文档时dict.copy(),它说它制作了该词典的浅表副本。我关注的书(Beazley的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()按我的预期无法正常工作?还是我对浅复制和深复制的理解存在缺陷?


2
奇怪的是他们不解释“浅”。内幕知识,眨眨眼。只是dict和key是副本,而第一层内部的嵌套dict是引用,例如,不能在循环中删除。因此,在这种情况下,Python的dict.copy()既无用也不直观。感谢您的提问。
gseattle

Answers:


990

“浅复制”表示字典的内容不是按值复制,而只是创建一个新引用。

>>> 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:参考分配,制造ab指向同一对象。

    '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]'的另一个实例。


好的答案,但是您可能会考虑纠正第一句话中的语法错误。并且没有理由不在中L再次使用b。这样做可以简化示例。
汤姆·罗素

@kennytm:实际上,前两个示例有什么区别?您得到的结果相同,但是内部实现略有不同,但是这有什么关系呢?
JavaSa

@TomRussell:还是任何人,由于这个问题已经很老了,我的澄清问题是针对所有人的
JavaSa

@JavaSa如果您这样做,就很重要b[1][0] = 5。如果b是浅表副本,则说明您已更改a[1][0]
汤姆·罗素

2
很好的解释,...真的救了我的一天!谢谢...可以在python的列表,str和其他数据类型中应用相同的内容吗?
布罗

38

这不是深拷贝或浅拷贝的问题,您要做的只是深拷贝。

这里:

>>> new = original 

您正在创建对原始引用的列表/字典的新引用。

而在这里:

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

您正在创建一个新的列表/字典,其中填充了原始容器中包含的对象引用的副本。


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

8
no change in original, since ['a'] is an immutable integer这个。它实际上回答了所问的问题。
CivFan

8

添加到肯尼的答案。当您进行浅表复制parent.copy()时,会使用相同的键创建一个新字典,但不会复制它们的值。如果将新值添加到parent_copy,则不会影响父对象,因为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]}

parent [1]parent_copy [1]的hash(id)值相同,这意味着存储在id 140690938288400中的parent [1]parent_copy [1]的 [1,2,3] 。

但是parentparent_copy的哈希值不同,这意味着它们是不同的字典,并且parent_copy是一个新字典,其值引用了parent的


5

“ new”和“ original”是不同的dict,这就是为什么您只能更新其中之一。.这些项目是浅复制的,而不是dict本身。


2

内容是浅复制的。

所以,如果原来的dict包含list或另一个dictionary,在原或其浅拷贝修改一个他们将修改他们(listdict)在其他。


1

在第二部分中,您应该使用 new = original.copy()

.copy=是不同的东西。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.