我理解浅层副本和深层副本之间的区别,就像我在课堂上学到的那样。但是以下没有意义
import copy
a = [1, 2, 3, 4, 5]
b = copy.deepcopy(a)
print(a is b)
print(a[0] is b[0])
----------------------------
~Output~
>False
>True
----------------------------
当对象及其构成元素在深层副本中的不同内存位置重新创建时,不应该{0}评估为False吗?我正在测试这个,因为我们在课堂上已经讨论过这个但它似乎没有用。
答案 0 :(得分:11)
这种行为的原因是Python优化了小整数,因此它们实际上并不在不同的内存位置。查看id
的{{1}},它们始终相同:
1
来自Integer Objects的参考:
当前实现为
>>> x = 1 >>> y = 1 >>> id(x) 1353557072 >>> id(y) 1353557072 >>> a = [1, 2, 3, 4, 5] >>> id(a[0]) 1353557072 >>> import copy >>> b = copy.deepcopy(a) >>> id(b[0]) 1353557072
和-5
之间的所有整数保留一个整数对象数组,当您在该范围内创建一个int时,实际上只返回对现有对象的引用。所以应该可以更改256
的值。我怀疑在这种情况下Python的行为是未定义的。 : - )
答案 1 :(得分:11)
余浩的回答其实并不正确。虽然Python确实为小整数实例化了对象,但并不是导致这种行为的原因。
让我们来看看当我们使用更大的整数时会发生什么。
> from copy import deepcopy
> x = 1000
> x is deepcopy(x)
True
如果我们深入了解copy
模块,我们发现调用具有原子值的deepcopy
会延迟调用函数_deepcopy_atomic
。
def _deepcopy_atomic(x, memo):
return x
所以实际发生的事情是deepcopy
不会复制不可变值,而只会返回它。
例如int
,float
,str
,function
等等。
答案 2 :(得分:1)
奥利维·梅兰松(OlivierMelançon)的答案是正确的,如果我们将其作为一个机械问题,即关于deepcopy
函数调用最终如何返回对相同int
对象而不是它们的副本的引用。我将退后一步,回答为什么这是deepcopy
要做的明智之举的问题。
我们需要复制数据结构的副本(深层副本或浅层副本)的原因是,我们可以在不影响原始状态的情况下修改其内容;或因此我们可以修改原始,同时仍然保留旧状态的副本。为此,当数据结构具有本身可变的嵌套部分时,就需要深拷贝。考虑以下示例,该示例将2D网格中的每个数字相乘,例如[[1, 2], [3, 4]]
:
import copy
def multiply_grid(grid, k):
new_grid = copy.deepcopy(grid)
for row in new_grid:
for i in range(len(row)):
row[i] *= k
return new_grid
诸如列表之类的对象是可变的,因此操作row[i] *= k
会更改其状态。复制列表是防御突变的一种方法。这里需要一个深层副本,以制作外部列表和内部列表(即行)的副本,它们也是可变的。
但是诸如整数和字符串之类的对象是不可变的,因此无法修改其状态。如果int
对象为13,那么即使将其乘以k
,它也会保持13。相乘会产生不同的int
对象。没有要防御的突变,因此无需复制。
有趣的是,deepcopy
不会复制元组的组成部分都是不变的,而在它们具有可变的组成部分时会复制:
>>> import copy
>>> x = ([1, 2], [3, 4])
>>> x is copy.deepcopy(x)
False
>>> y = (1, 2)
>>> y is copy.deepcopy(y)
True
逻辑是相同的:如果一个对象是不可变的,但是具有可变的嵌套组件,则需要一个副本来避免对原始组件的更改。但是,如果整个结构是完全不变的,则无需防御任何突变,因此无需复制。