在Python中复制嵌套列表

72

我想复制一个二维列表,这样如果我修改其中一个列表,另一个列表不会被修改。

对于一维列表,我只需要这样做:

a = [1, 2]
b = a[:]

现在,如果我修改ba不会被修改。

但是这对于二维列表并不起作用:

a = [[1, 2],[3, 4]]
b = a[:]

如果我修改 ba 也会被修改。

我该怎么解决这个问题?


7
很多时候,当人们使用嵌套列表并需要以这种方式复制它们时,他们实际上想要使用numpy - Mike Graham
1
在我看来,那只是语言中的一个错误。在应该相同的两种情况下表现不同的行为——这是解释型语言的典型特征。如果你有大量的代码,调试起来非常困难。 - Serhii
@SerhiiPoklonskyi 不,这不是一个错误。当你执行 b = a[:] 时,你创建了一个新的列表 b,所以例如 a.append([5, 6]) 将不会修改 b,因为它只是改变了 a。然而,行 a[1][0] = 5 改变 b,因为它改变了 b 引用的列表。 - Artemis
@SerhiiPoklonskyi 不太确定我完全理解了吗? - Artemis
4
我认为你感到困惑的原因是你误解了Python。 Python实际上没有所谓的“二维数组”,它只有列表,其中可以包含其他列表。我将通过一个例子来说明:你可以使用a = [[1, 2], [3, 4]] 定义变量 a,然后创建 a 的一个副本:b = a.copy()。这是一个不同的列表,但它包含相同的“子列表”。这意味着更改 b,例如 b.append([5, 6])不会更改 a,但是更改 b 中的一个列表,例如 b[0].append(3) 也会更改 a 中的第一个列表。 - Artemis
显示剩余7条评论
5个回答

90

若要获得一个更加通用的解决方案,不受维度数量限制,可以使用 copy.deepcopy()

import copy
b = copy.deepcopy(a)

2
@Dav,你说得很有道理。我更喜欢始终导入模块以避免名称冲突,而不是逐个处理函数。 :) - Ayman Hourieh
3
请注意,这也将深度复制列表中的实际元素。 - FogleBird
1
@Dav,我不同意,通常最好使用module.function()格式。 - FogleBird
5
"命名空间是一个极好的想法——让我们做更多这样的事情吧!" - user111086
1
@FogleBird:然而,PEP-8似乎确实暗示着from ... import ...是正常情况,除非存在命名空间冲突:http://www.python.org/dev/peps/pep-0008/(参见“Imports”)。 - Amber
显示剩余3条评论

88
b = [x[:] for x in a]

9
适当的话可以加1。我个人喜欢避免使用复制/深复制(在实际生活中非常少有合理的用例;对于我来说,多于2维的列表也是如此)。 - ChristopheD
1
请提供一个使用案例好吗?我正在尝试复制一个二维列表,但是我不确定应该用你提供的变量名替换哪些部分。 - John Locke
@JohnLocke b 是新列表,a 是旧的。x 在内部使用。 - Artemis
对我来说,path 是一个二维数组,我想复制 path[i],所以我做了 curr_level = [x[:] for x in path[i]] - mLstudent33
这就是我所说的高效(而且聪明)编程!(为了一些可以简单完成的事情而导入额外模块是低效的。) - Apostolos

5

为什么 b = a[:] 不适用于嵌套列表(或者说多维列表)?

a = [[1, 2],[3, 4]]
b = a[:]

答案:尽管我们使用切片操作[:] 复制列表a,但内部子列表仍然指向列表b的内部子列表。

注意:我们可以使用Python中的id()检查引用(reference)
让我们通过一个例子来理解。

>>> a = [[1,2],[3,4]]
>>> id(a)
140191407209856    # unique id of a
>>> b=a
>>> id(b)
140191407209856
>>> b=a[:]        # Copying list a to b with slicing
>>> id(b)         
140191407209920   # id of list b changed & is not same as id of list a
>>> id(a[0])      
140191407188544
>>> id(b[0])
140191407188544
>>> id(a[0])==id(b[0])  # id of both a[0] & b[1] is same.
True

因此,切片不会改变列表内部对象的引用。您可以从上面注意到a [0]的引用与b [0]相同。
当您将2D列表复制到另一个列表时,它会添加对其的引用而不是实际列表。

相反,您可以使用:

  • b = copy.deepcopy(a)
  • b = [item [:] for item in a]
  • b = [item.copy() for item in a]
  • b = [list(item) for item in a]
  • b = [copy.copy(item) for item in a]
  • b = []; b.extens[a]

下面是所有可用复制方法的时间复杂度比较source

  1. 10.59秒(每次105.9微秒) - copy.deepcopy(old_list)的深拷贝

  2. 10.16秒(每次101.6微秒) - 使用纯Python Copy() 方法复制类并进行深拷贝

  3. 1.488秒(每次14.88微秒) - 使用纯Python Copy() 方法不复制类(只复制字典/列表/元组)

  4. 0.325秒(每次3.25微秒) - for item in old_list: new_list.append(item)

  5. 0.217秒(每次2.17微秒) - [i for i in old_list](一个列表推导式

  6. 0.186秒(每次1.86微秒) - copy.copy(old_list)的浅拷贝

  7. 0.075秒(每次0.75微秒) - list(old_list)

  8. 0.053秒(每次0.53微秒) - new_list = []; new_list.extend(old_list)

  9. 0.039秒(每次0.39微秒) - old_list[:]列表切片


“b = []; b.extend[a]”在这里行不通 - 它是一个浅拷贝,就像“b = [item for item in a]”一样。 - slothrop

0
它不起作用是因为当你进行克隆或复制时,它会创建浅拷贝。那么什么是浅拷贝和深拷贝呢?
考虑一下,a是一个列表,a = [1,2,3],现在当你创建一个新的名字b = a[:]或b = a.copy()。(两者都会得到相同的结果)。你需要理解在内存层面上发生了什么。当我们创建副本时,列表a和b的地址引用将会改变,因为在Python中一切都是按引用传递的。然而,列表中项目的地址将保持不变。请参考下面的代码片段。
[创建列表并打印元素的id] 1 现在,我们将创建一个副本并打印副本对象内部元素的id。 你可以看到在创建副本后,列表内部对象的id没有改变。 然而,两个列表的id将不同,如下所示:
[Id不同] 3 这被称为浅拷贝,其中可变数据类型内部对象的地址不会改变。因此,如果您将可变数据类型传递给另一个可变数据类型(即2D列表),则内部可变数据类型的地址将保持相同。因此,使用名称a进行的更改也将反映在b中。请参阅下面的代码片段。
[2D列表概述] 4 如果您的要求是不应有任何更改,则可以使用深拷贝,如下所示的代码片段。
[深拷贝] 5

0
您可以使用以下代码来处理二维嵌套数组: b = [[y for y in x] for x in a]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接