为什么整数列表的深度复制会在内存中返回相同的整数?

时间:2015-07-25 01:42:09

标签: python python-3.x deep-copy

我理解浅层副本和深层副本之间的区别,就像我在课堂上学到的那样。但是以下没有意义

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吗?我正在测试这个,因为我们在课堂上已经讨论过这个但它似乎没有用。

3 个答案:

答案 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不会复制不可变值,而只会返回它。

例如intfloatstrfunction等等。

答案 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

逻辑是相同的:如果一个对象是不可变的,但是具有可变的嵌套组件,则需要一个副本来避免对原始组件的更改。但是,如果整个结构是完全不变的,则无需防御任何突变,因此无需复制。