python copy.deepcopy 列表看起来很浅

Tsf*_*Tsf 1 python deep-copy python-3.x

我正在尝试初始化表示 3x3 数组的列表列表:

import copy
m = copy.deepcopy(3*[3*[0]])
print(m)
m[1][2] = 100
print(m)
Run Code Online (Sandbox Code Playgroud)

输出是:

[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 100], [0, 0, 100], [0, 0, 100]]
Run Code Online (Sandbox Code Playgroud)

这不是我所期望的,因为每行的最后一个元素是共享的!我确实通过使用得到了我需要的结果:

m = [ copy.deepcopy(3*[0]) for i in range(3) ]
Run Code Online (Sandbox Code Playgroud)

但我不明白为什么第一个(和更简单的)形式不起作用。不是deepcopy应该很深吗?

MSe*_*ert 6

问题是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]]
Run Code Online (Sandbox Code Playgroud)

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

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

m = [[0]*3 for _ in range(3)]
Run Code Online (Sandbox Code Playgroud)

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

m = [[0 for _ in range(3)] for _ in range(3)] 
Run Code Online (Sandbox Code Playgroud)