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


当前回答

在Python中,请记住:

    list1 = ['apples','bananas','pineapples']
    list2 = list1

List2没有存储实际的列表,而是对list1的引用。因此,当您对list1执行任何操作时,list2也会发生变化。使用copy模块(非默认,在pip上下载)制作列表的原始副本(对于简单列表,copy.copy();对于嵌套列表,copy。deepcopy())。这将生成一个不会随第一个列表而更改的副本。

其他回答

Python 3.6计时

下面是使用Python 3.6.8的计时结果。请记住,这些时间是相对的,而不是绝对的。

我坚持只做浅层复制,还添加了一些在Python 2中不可能的新方法,例如list.copy()(Python 3切片的等价物)和两种形式的列表解包(*new_list,=list和new_list=[*list]):

METHOD                TIME TAKEN
b = [*a]               2.75180600000021
b = a * 1              3.50215399999990
b = a[:]               3.78278899999986  # Python 2 winner (see above)
b = a.copy()           4.20556500000020  # Python 3 "slice equivalent" (see above)
b = []; b.extend(a)    4.68069800000012
b = a[0:len(a)]        6.84498999999959
*b, = a                7.54031799999984
b = list(a)            7.75815899999997
b = [i for i in a]    18.4886440000000
b = copy.copy(a)      18.8254879999999
b = []
for item in a:
  b.append(item)      35.4729199999997

我们可以看到,Python 2的获胜者仍然表现出色,但并没有远远超过Python 3 list.copy(),特别是考虑到后者的出色可读性。

黑马是拆包和重新包装方法(b=[*a]),它比原始切片快约25%,比其他拆包方法(*b,=a)快两倍多。

b=a*1的表现也出奇地好。

请注意,这些方法不会为列表以外的任何输入输出等效结果。它们都适用于可切片对象,少数适用于任何可迭代对象,但只有copy.copy()适用于更一般的Python对象。


以下是相关方的测试代码(此处的模板):

import timeit

COUNT = 50000000
print("Array duplicating. Tests run", COUNT, "times")
setup = 'a = [0,1,2,3,4,5,6,7,8,9]; import copy'

print("b = list(a)\t\t", timeit.timeit(stmt='b = list(a)', setup=setup, number=COUNT))
print("b = copy.copy(a)\t", timeit.timeit(stmt='b = copy.copy(a)', setup=setup, number=COUNT))
print("b = a.copy()\t\t", timeit.timeit(stmt='b = a.copy()', setup=setup, number=COUNT))
print("b = a[:]\t\t", timeit.timeit(stmt='b = a[:]', setup=setup, number=COUNT))
print("b = a[0:len(a)]\t\t", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\t\t\t", timeit.timeit(stmt='*b, = a', setup=setup, number=COUNT))
print("b = []; b.extend(a)\t", timeit.timeit(stmt='b = []; b.extend(a)', setup=setup, number=COUNT))
print("b = []; for item in a: b.append(item)\t", timeit.timeit(stmt='b = []\nfor item in a:  b.append(item)', setup=setup, number=COUNT))
print("b = [i for i in a]\t", timeit.timeit(stmt='b = [i for i in a]', setup=setup, number=COUNT))
print("b = [*a]\t\t", timeit.timeit(stmt='b = [*a]', setup=setup, number=COUNT))
print("b = a * 1\t\t", timeit.timeit(stmt='b = a * 1', setup=setup, number=COUNT))

在已经给出的答案中,缺少了一个独立于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

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

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

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)

Python的习惯用法是newList=oldList[:]

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

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)

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