python copy.deepcopy列表似乎很浅

时间:2017-08-22 14:36:50

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

我正在尝试初始化代表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应该很深?

3 个答案:

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

忽略该课程没有多大意义,因为它故意在其xx1以及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!