在一些地方阅读后,包括: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}
这是否意味着浅层副本和深层副本不一定会影响不可变值?
答案 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
,而是
a
访问绑定到的对象然后我们 会影响a
,因为绑定到b
的对象将发生突变:
b
总之,您必须了解
绑定和突变之间的区别。前者影响变量(或更普遍地说是位置),而后者影响对象。关键在于其中
重新绑定名称(或一般位置)不会影响该名称先前绑定到的对象(除了更改其引用计数外)。
现在,在您的示例中,您将创建(在概念上)如下所示的内容:
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 返回一个新的相同对象,但是其中的值也已被递归复制。