【问题标题】:python copy.deepcopy lists seems shallowpython copy.deepcopy 列表似乎很浅
【发布时间】:2017-08-22 14:36:49
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

    问题在于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]]
    

    忽略该类没有多大意义,因为它故意在其xx1x2 属性之间共享引用。如果deepcopy 通过单独复制这些引用来破坏这些共享引用,那就太奇怪了。这就是为什么文档将其称为“复制太多,例如打算在副本之间共享的数据”问题的“解决方案”。

    回到您的示例:如果您不不想共享引用,最好完全避免它们:

    m = [[0]*3 for _ in range(3)]
    

    在您的情况下,内部元素是不可变的,因为 0 是不可变的 - 但是如果您在最里面的列表中处理可变实例,您还必须避免内部列表乘法:

    m = [[0 for _ in range(3)] for _ in range(3)] 
    

    【讨论】:

    • 您对 deepcopy 无法正常工作的原因进行了“操作”解释。但是文档说(强调我的):“深层副本构造一个新的复合对象,然后递归地将副本插入到原始对象中。”我觉得很不满意!
    • 是的,但它比陷入无限循环要好,因为你有一个引用循环或递归数据结构。 Python 开发人员记录了这种行为——因此我们作为最终用户的工作是避免不必要的共享引用。 :)
    • 同意不同意!现在我了解了 deepcopy 的这种行为,我会更加小心。 ;)
    • @Tsf 这听起来很合理。这是一个设计决定,无论我们喜欢还是讨厌它,我们都必须忍受它。您是否考虑过接受某个答案,或者没有一个答案足以解决您的问题?
    • 我猜是“deepcopy”这个名字让我得出了不同的结论。我的问题的一个明显的通用解决方案是编写我自己的函数,它首先检查参数并在它是递归的时引发异常(就像函数 PrettyPrinter.isrecursive(obj) 一样)。否则它会递归地复制所有内容。无论如何,这个讨论非常有用。
    【解决方案2】:

    问题是您创建了一个包含 3 次相同对象的列表,因此当您在其中一个列表中分配一个值时,它会影响所有列表(因为它是相同的)。

    尝试做:

    a = [[3*[0]] for i in range(3)]
    m = copy.deepcopy(a)
    

    在这里创建“a”,它是一个由 3 个大小为 3 的列表组成的列表,初始化为 0。深拷贝“a”会给你“m”——和“a”一样,但是不同的对象,所以改变“a”不会影响“m”,反之亦然。

    【讨论】:

      【解决方案3】:

      在阅读了几个答案后,我对这个问题有了更多的思考。问题在于递归(循环)对象无法被深度复制!例如:

      x = [1]
      x.append(x)
      

      产生一个对象,其行为类似于一个无限的 1 序列,Python 将其打印为:

      [1, [...]]
      

      deepcopy(x) 也是如此。在我看来,Python 实现采用了一种解决方案,该解决方案避免了无限循环,但对于没有循环但具有共享组件的对象可能会产生不正确的结果。我宁愿看到我的程序永远循环并修复它,而不是寻找一个不起眼的错误!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-16
        • 1970-01-01
        • 2016-12-14
        • 1970-01-01
        相关资源
        最近更新 更多