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


当前回答

还有另一种方法可以复制一个直到现在才列出的列表:添加一个空列表:l2=l+[]。

我用Python 3.8测试了它:

l = [1,2,3]
l2 = l + []
print(l,l2)
l[0] = 'a'
print(l,l2)

这不是最好的答案,但它奏效了。

其他回答

new_list = my_list[:]

new_list=我的列表

试着理解这一点。假设my_list位于堆内存中的位置X,即my_list指向X。现在,通过指定new_list=my_list,可以让new_list指向X。这就是所谓的浅拷贝。

现在,如果指定new_list=my_list[:],则只需将my_list的每个对象复制到new_list。这就是所谓的深度复制。

您可以通过以下其他方式完成此操作:

new_list=列表(old_list)导入副本new_list=复制.depcopy(old_list)

已经有很多答案告诉你如何制作一个正确的副本,但没有一个答案说明为什么你的原始“副本”失败了。

Python不在变量中存储值;它将名称绑定到对象。您的原始赋值接受my_list引用的对象,并将其绑定到new_list。无论使用哪一个名称,仍然只有一个列表,因此当将其引用为my_list时所做的更改将在将其引用成new_list时保持不变。这个问题的每个其他答案都为您提供了创建新对象以绑定到new_list的不同方法。

列表中的每个元素都像一个名称,因为每个元素都以非独占方式绑定到一个对象。浅层副本创建一个新列表,其元素绑定到与之前相同的对象。

new_list = list(my_list)  # or my_list[:], but I prefer this syntax
# is simply a shorter way of:
new_list = [element for element in my_list]

要使列表副本更进一步,请复制列表引用的每个对象,并将这些元素副本绑定到新列表。

import copy  
# each element must have __copy__ defined for this...
new_list = [copy.copy(element) for element in my_list]

这还不是深度复制,因为列表的每个元素都可能引用其他对象,就像列表绑定到其元素一样。要递归复制列表中的每个元素,然后复制每个元素引用的每个其他对象,依此类推:执行深度复制。

import copy
# each element must have __deepcopy__ defined for this...
new_list = copy.deepcopy(my_list)

有关复制中的角盒的详细信息,请参阅文档。

对每种复制模式的简短解释:

浅层副本构造一个新的复合对象,然后(在可能的范围内)向其中插入对原始对象的引用-创建浅层副本:

new_list = my_list

深度副本构造一个新的复合对象,然后递归地将原始对象的副本插入其中,从而创建一个深度副本:

new_list = list(my_list)

list()适用于简单列表的深度复制,例如:

my_list = ["A","B","C"]

但是,对于复杂的列表,如。。。

my_complex_list = [{'A' : 500, 'B' : 501},{'C' : 502}]

…使用deepcopy():

import copy
new_complex_list = copy.deepcopy(my_complex_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使用递归来复制对象,并且只返回与以前相同的不可变对象,因为不可变对象无法复制。然而,它深度复制可变对象的最内层,直到到达对象的最外层。

通过id和gc查看内存的一个稍微实用的视角。

>>> b = a = ['hell', 'word']
>>> c = ['hell', 'word']

>>> id(a), id(b), id(c)
(4424020872, 4424020872, 4423979272) 
     |           |
      -----------

>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # all referring to same 'hell'
     |           |           |
      -----------------------

>>> id(a[0][0]), id(b[0][0]), id(c[0][0])
(4422785208, 4422785208, 4422785208) # all referring to same 'h'
     |           |           |
      -----------------------

>>> a[0] += 'o'
>>> a,b,c
(['hello', 'word'], ['hello', 'word'], ['hell', 'word'])  # b changed too
>>> id(a[0]), id(b[0]), id(c[0])
(4424018384, 4424018384, 4424018328) # augmented assignment changed a[0],b[0]
     |           |
      -----------

>>> b = a = ['hell', 'word']
>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # the same hell
     |           |           |
      -----------------------

>>> import gc
>>> gc.get_referrers(a[0]) 
[['hell', 'word'], ['hell', 'word']]  # one copy belong to a,b, the another for c
>>> gc.get_referrers(('hell'))
[['hell', 'word'], ['hell', 'word'], ('hell', None)] # ('hello', None)