关于python:了解dict.copy() – shallow or deep?

Understanding dict.copy() - shallow or deep?

在阅读dict.copy()的文档时,它说它只是一个简单的字典副本。我下面这本书也是如此(Beazley的python参考书),上面说:

The m.copy() method makes a shallow
copy of the items contained in a
mapping object and places them in a
new mapping object.

考虑一下:

1
2
3
4
5
6
7
>>> 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),因为我做的是一个浅拷贝。就像如果你做一个列表:

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

这按预期工作。

既然两者都是浅薄的拷贝,为什么dict.copy()不能像我预期的那样工作?或者我对浅拷贝和深拷贝的理解有缺陷?


"浅复制"意味着字典的内容不是按值复制的,而是创建一个新的引用。

1
2
3
4
5
6
7
>>> 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]})

相反,深度复制将按值复制所有内容。

1
2
3
4
5
6
7
>>> 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]})

所以:

  • b = a:参照赋值,使ab指向同一对象。

    Illustration of 'a = b': 'a' and 'b' both point to '{1: L}', 'L' points to '[1, 2, 3]'.

  • b = a.copy():浅复制,ab将成为两个孤立的对象,但它们的内容仍然具有相同的参考。

    Illustration of 'b = a.copy()': 'a' points to '{1: L}', 'b' points to '{1: M}', 'L' and 'M' both point to '[1, 2, 3]'.

  • b = copy.deepcopy(a):深度复制,ab的结构和内容完全分离。

    Illustration of 'b = copy.deepcopy(a)': 'a' points to '{1: L}', 'L' points to '[1, 2, 3]'; 'b' points to '{1: M}', 'M' points to a different instance of '[1, 2, 3]'.


  • 这不是深度复制或浅复制的问题,你所做的都不是深度复制。

    在这里:

    1
    >>> new = original

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

    在这里:

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

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


    举个例子:

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

    现在,让我们在"浅层"(第一层)中更改一个值:

    1
    2
    3
    4
    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

    现在让我们更深入地改变一个值:

    1
    2
    3
    4
    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


    增加了肯尼特的答案。当您执行shallow copy parent.copy()时,将使用相同的键创建一个新字典,但不会复制这些值。如果您向parent_copy添加一个新值,则不会影响parent,因为parent_copy是一个新字典,而不是引用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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]}

    父级[1]、父级副本[1]的哈希(ID)值相同,这意味着存储在ID 140690938288400的父级[1]和父级副本[1]的[1,2,3]。

    但是父级和父级副本的哈希是不同的,这意味着它们是不同的字典,parent_copy是一个新的字典,它的值引用了parent的值。


    "new"和"original"是不同的dict,这就是为什么您可以只更新其中一个。这些项目只是简单的复制,而不是听写本身。


    内容是浅拷贝的。

    因此,如果原始的dict包含一个list或另一个dictionary,在原始的或其浅薄的副本中修改一个将在另一个中修改它们(listdict


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

    .copy=是不同的东西。