python3 dict.copy仍然只创建浅表副本吗?

时间:2018-06-27 18:36:12

标签: python deep-copy

在一些地方阅读后,包括:Understanding dict.copy() - shallow or deep?

它声称dict.copy将创建一个浅表副本,否则称为对相同值的引用。但是,当我自己在python3 repl中玩游戏时,我只能按值获得副本吗?

a = {'one': 1, 'two': 2, 'three': 3}
b = a.copy()

print(a is b) # False
print(a == b) # True

a['one'] = 5
print(a) # {'one': 5, 'two': 2, 'three': 3}
print(b) # {'one': 1, 'two': 2, 'three': 3}

这是否意味着浅层副本和深层副本不一定会影响不可变值?

4 个答案:

答案 0 :(得分:3)

整数是不可变的,引用对象时会出现问题,请检查以下类似示例:

import copy
a = {'one': [], 'two': 2, 'three': 3}
b = a.copy()
c = copy.deepcopy(a)
print(a is b) # False
print(a == b) # True

a['one'].append(5)
print(a) # {'one': [5], 'two': 2, 'three': 3}
print(b) # {'one': [5], 'two': 2, 'three': 3}
print(c) # {'one': [], 'two': 2, 'three': 3}

这里有live

答案 1 :(得分:1)

您正在观察的内容与字典完全无关。您对 binding mutation 之间的区别感到困惑。

首先让我们忘记字典,并用简单的变量演示问题。一旦了解了基本点,便可以回到字典示例中。

a = 1
b = a
a = 2
print(b)  # prints 1
  • 在第一行上,在名称a和对象1之间创建绑定。
  • 在第二行上,在名称b和表达式a ...的值之间创建一个绑定,该表达式与绑定到名称的对象1完全相同a在上一行。
  • 在第三行上,您在名称a和对象2之间创建了绑定,在此过程中,忘记了a和{{1}之间曾经存在绑定}。

请务必注意,最后一步不能以任何方式影响1

这种情况是完全对称的,因此如果第3行为b,则对b = 2绝对没有影响。

现在,人们经常误认为这是整数不可变性的结果。整数 在Python中是不可变的,但这是完全不相关的。如果我们对某些可变对象(例如列表)执行类似操作,那么我们将获得同等的结果。

a

再次

  • a = [1] b = a a = [2] print(b) # prints [1] 绑定到某个对象
  • a绑定到同一对象
  • b现在反弹到其他物体

不能以任何方式影响a或其绑定的对象[*]!没有在任何地方进行任何突变的尝试,因此可变性与这种情况完全无关。

[*]实际上,它确实会更改对象的引用计数(至少在CPython中),但这实际上不是对象的可观察属性。

但是,如果我们不是重新绑定 b,而是

  1. 使用a访问绑定到的对象
  2. 更改该对象

然后我们 会影响a,因为绑定到b的对象将发生突变:

b

总之,您必须了解

  1. 绑定突变之间的区别。前者影响变量(或更普遍地说是位置),而后者影响对象。关键在于其中

  2. 重新绑定名称(或一般位置)不会影响该名称先前绑定到的对象(除了更改其引用计数外)。

现在,在您的示例中,您将创建(在概念上)如下所示的内容:

a = [1]
b = a
a[0] = 2
print(b)  # prints [2]

,然后a ---> { 'three' ----------------------> 3 'two' -------------> 2 ^ 'one' ---> 1 } ^ | ^ | | | | | b ---> { 'one' ----- | | 'two' --------------- | 'three' ------------------------- 只需重新绑定位置a['one'] = 5,以使其不再绑定到a['one'],而是绑定到1。换句话说,从第一个5出来的箭头现在指向其他地方。

重要的是要记住,这与整数的不变性完全无关。如果使示例中的每个整数都可变(例如,用包含它的列表替换它:例如,将'one'的每个出现都替换为1(对于[1]2)),那么您仍然会观察到基本上相同的行为:3不会影响a['one'] = [1]的值。

现在,在这个最新示例中,存储在字典中的值是列表,因此具有结构性,可以区分浅拷贝和深拷贝:

  • b['one']完全不会复制字典:它只会使b = a成为对同一单个字典的新绑定
  • b将创建一个新字典,该字典具有对相同列表的内部绑定。复制字典,但新字典仅引用其内容(位于顶层以下)。
  • b = copy.copy(b)还将创建一个新的字典,但也会创建新的对象以填充该字典,而不是引用原始的字典。

因此,如果您在浅拷贝的情况下进行变异(而不是 rebind ),则另一本词典将“看到”变异,因为这两个词典共享对象。在深拷贝中不会发生这种情况。

答案 2 :(得分:1)

请考虑解释这种情况,因此您将能够轻松理解引用和 copy() 方法。

    dic = {'data1': 100, 'data2': -54, 'data3': 247}
    dict1 = dic
    dict2 = dic.copy()
    print(dict2 is dic)
    # False
    print(dict1 is dic)
    # true

第一个打印语句打印错误,因为 dict2 和 dic 是两个独立的字典,具有不同的内存空间,即使它们具有相同的内容。当我们使用复制功能时会发生这种情况。 其次,当将 dic 分配给 dict1 时,不会创建具有单独内存空间的单独字典,而是 dict1 引用 dic。

答案 3 :(得分:0)

某个容器的副本意味着返回了一个新的相同对象,但其值是相同的对象。

这意味着对副本的进行变异将对原始文档的进行变异。在您的示例中,您没有在更改值,而是在更新键。

这是价值突变的一个例子。

d = {'a': []}

d_copy = d.copy()

print(d is d_copy) # False
print(d['a'] is d['a']) # True

d['a'].append(1)
print(d_copy) # {'a': [1]}

另一方面,容器的 deepcopy 返回一个新的相同对象,但是其中的值也已被递归复制。