如何制作一个完全不共享的复杂列表副本?(深复制不够)

6 投票
5 回答
10902 浏览
提问于 2025-04-15 15:14

看看这段Python代码:

a = [1, 2, 3]
b = [4, 5, 6]
c = [[a, b], [b, a]] # [[[1, 2, 3], [4, 5, 6]], [[4, 5, 6], [1, 2, 3]]]
c[0][0].append(99)   # [[[1, 2, 3, 99], [4, 5, 6]], [[4, 5, 6], [1, 2, 3, 99]]]

注意,当你修改了c中的一个元素时,其他地方也会跟着改变。比如,如果你在c[0][0]后面加上99,那么c[1][1]也会变成一样的。这是因为Python很聪明,它实际上是让c[0][0]c[1][1]指向同一个东西。(也就是说,它们的id()是一样的。)

问题:有没有什么办法可以让c的列表元素可以安全地在本地修改?上面的例子只是个简单的说明,我真正的问题是有一个更复杂的列表,但也遇到了类似的问题。

(抱歉上面的问题表述得不好。Python高手们请随意修改问题或标签,以更好地表达这个疑问。)

5 个回答

5

根据你的情况,你可能想要对这个列表进行一个深拷贝

8

当你想要一个副本时,你需要明确地去复制。那种神秘的 [:] “切片全部”的写法虽然是常见用法,但我更喜欢直接调用 list 这种更容易理解的方法。

如果 c 的构建方式不对(用的是引用而不是你想要独立修改的列表的浅拷贝),那么最好的办法就是修正它的构建方式(为什么要先建错再费力去修呢?)。不过如果这超出了你的控制范围,你还是可以修复这个问题——只需对 c 进行循环(如果需要的话可以递归),用一个索引重新分配相关的子列表为它们的副本。例如,如果你确定 c 的结构是你所说的两层结构,你可以在不使用递归的情况下解决这个问题:

def fixthewronglymadelist(c):
  for topsublist in c:
    for i, L in enumerate(topsublist):
      topsublist[i] = list(L)

尽管其他答案可能会建议使用 copy.deepcopy,但如果你只有错误构建的 c,那么用它来达到这个特殊目的会很困难:仅仅执行 copy.deepcopy(c) 会小心翼翼地复制 c 的结构,包括对同一子列表的多个引用!:-)

8

如果你想把一个已经存在的列表(里面还有列表)转换成一个没有任何共享部分的新列表,你可以使用递归的方法来复制这个列表。

单纯使用deepcopy是不够的,因为它只是把结构原封不动地复制过来,里面的引用仍然指向原来的地方,而不是新复制的内容。

def unshared_copy(inList):
    if isinstance(inList, list):
        return list( map(unshared_copy, inList) )
    return inList

alist = unshared_copy(your_function_returning_lists())

需要注意的是,这里假设数据是以列表的形式返回的(可以是任意层级嵌套的列表)。如果里面的容器类型不同(比如numpy数组、字典或者自定义类),你可能需要对这个方法进行一些调整。

撰写回答