我正在尝试初始化代表3x3数组的列表列表:
import copy
m = copy.deepcopy(3*[3*[0]])
print(m)
m[1][2] = 100
print(m)
,输出为:
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 100], [0, 0, 100], [0, 0, 100]]
这不是我所期望的,因为每行的最后一个元素是共享的!我确实得到了我需要的结果:
m = [ copy.deepcopy(3*[0]) for i in range(3) ]
但我不明白为什么第一个(和更简单的)形式不起作用。是不是deepcopy
应该很深?
答案 0 :(得分:4)
问题是deepcopy
保留了memo
,其中包含已经复制过的所有实例。这是为了避免无限递归和有意共享对象。因此,当它尝试对第二个子列表进行深度复制时,它会看到它已经复制了它(第一个子列表)并再次插入第一个子列表。简而言之,deepcopy
并未解决"共享子列表"问题!
引用文档:
深拷贝操作经常存在两个问题:浅拷贝操作不存在:
- 递归对象(直接或间接包含对自身的引用的复合对象)可能会导致递归循环。
- 因为深层复制会复制可能复制的所有内容,例如要在副本之间共享的数据。
deepcopy()函数通过以下方式避免了这些问题:
- 保留当前复制过程中已复制的对象的“备忘录”字典;和
- 让用户定义的类覆盖复制操作或复制的组件集。
(强调我的)
这意味着deepcopy
将共享参考视为意图。例如,考虑类:
from copy import deepcopy
class A(object):
def __init__(self, x):
self.x = x
self.x1 = x[0] # intentional sharing of the sublist with x attribute
self.x2 = x[1] # intentional sharing of the sublist with x attribute
a1 = A([[1, 2], [2, 3]])
a2 = deepcopy(a1)
a2.x1[0] = 10
print(a2.x)
# [[10, 2], [2, 3]]
忽略该课程没有多大意义,因为它故意在其x
和x1
以及x2
属性之间共享引用。如果deepcopy
通过单独复制每个共享引用来破坏这些共享引用,那将会很奇怪。这就是为什么文档提到这个"解决方案"解决了复制过多的问题,例如打算在副本之间共享的数据。"。
回到你的例子:如果你没有希望拥有共享引用,那么最好完全避免它们:
m = [[0]*3 for _ in range(3)]
在你的情况下,内部元素是不可变的,因为0
是不可变的 - 但是如果你处理最里面列表中的可变实例,你必须要避免内部列表乘法:
m = [[0 for _ in range(3)] for _ in range(3)]
答案 1 :(得分:1)
问题是你创建了一个相同对象的3倍的列表,所以当你在其中一个列表中分配一个值时,它会影响所有这些(因为它是相同的)。
尝试:
a = [[3*[0]] for i in range(3)]
m = copy.deepcopy(a)
在这里创建" a",这是3个大小为3的列表的列表,用0' s初始化。深刻复制" a"会给你" m" - 与" a"相同,但不同的对象,以便改变" a"不会影响" m"反之亦然。
答案 2 :(得分:-2)
在我读了几个答案后,我想到了更多关于这个问题的内容。问题在于无法对递归(循环)对象进行深度复制!例如:
x = [1]
x.append(x)
生成一个对象,其行为类似于1的无限序列,Python打印为:
[1, [...]]
deepcopy(x)
也是如此。在我看来,Python实现采用了一种避免无限循环的解决方案,但是对于没有圆形但具有共享组件的对象可能会产生不正确的结果。我宁愿永远看到我的程序循环并修复它,而不是搜索一个不起眼的bug!