使用new_list=my_list时,对new_list的任何修改都会每次更改my_list。为什么会出现这种情况,以及如何克隆或复制列表以防止出现这种情况?


当前回答

有人告诉我Python 3.3+添加了list.copy()方法,它应该和切片一样快:

newlist = old_list.copy()

其他回答

在已经给出的答案中,缺少了一个独立于python版本的非常简单的方法,您可以在大多数时间使用(至少我这样做):

new_list = my_list * 1       # Solution 1 when you are not using nested lists

但是,如果my_list包含其他容器(例如,嵌套列表),则必须按照复制库中上述答案中的其他建议使用deepcopy。例如:

import copy
new_list = copy.deepcopy(my_list)   # Solution 2 when you are using nested lists

。奖励:如果您不想复制元素,请使用(AKA浅层复制):

new_list = my_list[:]

让我们了解解决方案#1和解决方案#2之间的区别

>>> a = range(5)
>>> b = a*1
>>> a,b
([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])
>>> a[2] = 55
>>> a,b
([0, 1, 55, 3, 4], [0, 1, 2, 3, 4])

正如您所看到的,当我们不使用嵌套列表时,解决方案#1工作得很好。让我们检查一下当我们将解决方案#1应用于嵌套列表时会发生什么。

>>> from copy import deepcopy
>>> a = [range(i,i+4) for i in range(3)]
>>> a
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> b = a*1
>>> c = deepcopy(a)
>>> for i in (a, b, c): print i
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> a[2].append('99')
>>> for i in (a, b, c): print i
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]   # Solution #1 didn't work in nested list
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]       # Solution #2 - DeepCopy worked in nested list

我想发布一些不同于其他答案的内容。尽管这很可能不是最容易理解或最快的选项,但它提供了深度复制工作方式的一些内部视图,同时也是深度复制的另一种选择。我的函数是否有bug其实并不重要,因为这是为了展示一种复制问题答案之类的对象的方法,同时也是为了解释deepcopy的核心工作原理。

任何深度复制功能的核心都是创建浅层复制的方法。怎样易于理解的任何深度复制函数都只复制不可变对象的容器。当您深度复制嵌套列表时,您只复制外部列表,而不是列表内部的可变对象。您只是在复制容器。这同样适用于课堂。当您深度复制一个类时,您将深度复制它的所有可变属性。那么,如何?为什么你只需要复制容器,比如列表、字典、元组、迭代、类和类实例?

这很简单。可变对象不能真正复制。它永远无法更改,因此它只是一个值。这意味着您永远不必复制字符串、数字、布尔值或其中任何一个。但如何复制容器?易于理解的您只需要使用所有值初始化一个新容器。深度复制依赖于递归。它复制所有容器,甚至是其中有容器的容器,直到没有容器被留下。容器是一个不可变的对象。

一旦知道了这一点,完全复制一个没有任何引用的对象是非常容易的。这里有一个用于深度复制基本数据类型的函数(不适用于自定义类,但您可以随时添加)

def deepcopy(x):
  immutables = (str, int, bool, float)
  mutables = (list, dict, tuple)
  if isinstance(x, immutables):
    return x
  elif isinstance(x, mutables):
    if isinstance(x, tuple):
      return tuple(deepcopy(list(x)))
    elif isinstance(x, list):
      return [deepcopy(y) for y in x]
    elif isinstance(x, dict):
      values = [deepcopy(y) for y in list(x.values())]
      keys = list(x.keys())
      return dict(zip(keys, values))

Python自己的内置deepcopy就是基于这个例子。唯一的区别是它支持其他类型,并且通过将属性复制到新的重复类中来支持用户类,并且还通过引用已经使用备忘录列表或字典看到的对象来阻止无限递归。这就是制作深度副本的真正原因。从其核心来看,制作深度副本只是制作浅层副本。我希望这个答案能为这个问题增添一些东西。

示例

假设您有以下列表:[1,2,3]。不可变的数字不能重复,但另一层可以。您可以使用列表理解复制它:[1,2,3]中的x代表x]

现在,假设您有一个列表:[1,2],[3,4],[5,6]。这一次,您需要创建一个函数,它使用递归来深度复制列表的所有层。代替之前的列表理解:

[x for x in _list]

它使用新的列表:

[deepcopy_list(x) for x in _list]

deepcopy_list如下所示:

def deepcopy_list(x):
  if isinstance(x, (str, bool, float, int)):
    return x
  else:
    return [deepcopy_list(y) for y in x]

现在,您有了一个函数,它可以使用递归将str、bools、floast、int甚至列表的任何列表深度复制到无限多个层。这就是深度复制。

TLDR:Depcopy使用递归来复制对象,并且只返回与以前相同的不可变对象,因为不可变对象无法复制。然而,它深度复制可变对象的最内层,直到到达对象的最外层。

使用对象[:]

>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>> 

这是因为,行new_list=my_list为变量my_list分配了一个新的引用,即new_list这类似于下面给出的C代码,

int my_list[] = [1,2,3,4];
int *new_list;
new_list = my_list;

您应该使用复制模块创建新列表

import copy
new_list = copy.deepcopy(my_list)

所有其他贡献者都给出了很好的答案,当你有一个单一维度(水平化)列表时,这些方法是有效的,但是在目前提到的方法中,只有copy.deepcopy()可以克隆/复制列表,而当你使用多维嵌套列表(列表列表)时,它不会指向嵌套列表对象。虽然菲利克斯·克林在他的回答中提到了这一点,但这个问题还有一点问题,可能还有一个使用内置程序的解决方案,这可能会证明是深度复制的更快替代方案。

虽然new_list=old_list[:],copy.copy(old_list)'和Py3k old_list.copy()适用于单层列表,但它们恢复为指向嵌套在old_list和new_list中的列表对象,对其中一个列表对象的更改将在另一个列表中永久化。

编辑:新信息曝光

正如Aaron Hall和PM 2Ring所指出的那样,使用eval()不仅是一个坏主意,而且比copy.deepcopy()慢得多。这意味着,对于多维列表,唯一的选项是copy.deepcopy()。尽管如此,当您尝试在中等大小的多维数组上使用它时,它确实不是一个选项,因为性能会下降。我尝试使用42x42阵列来计时,这是前所未闻的,甚至对于生物信息学应用程序来说也是如此之大,我放弃了等待响应,只是开始在这篇文章中输入我的编辑。似乎唯一真正的选择就是初始化多个列表并独立处理它们。如果有人对如何处理多维列表复制有任何其他建议,将不胜感激。

正如其他人所说的那样,在多维列表中使用copy模块和copy.devcopy存在严重的性能问题。