使用new_list=my_list时,对new_list的任何修改都会每次更改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)

其他回答

让我惊讶的是,这还没有被提及,所以为了完整起见。。。

您可以使用“splat operator”:*执行列表解包,这也将复制列表的元素。

old_list = [1, 2, 3]

new_list = [*old_list]

new_list.append(4)
old_list == [1, 2, 3]
new_list == [1, 2, 3, 4]

这种方法的明显缺点是它仅在Python 3.5+中可用。

尽管从时间上看,这似乎比其他常用方法表现得更好。

x = [random.random() for _ in range(1000)]

%timeit a = list(x)
%timeit a = x.copy()
%timeit a = x[:]

%timeit a = [*x]

#: 2.47 µs ± 38.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.47 µs ± 54.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.39 µs ± 58.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

#: 2.22 µs ± 43.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

使用对象[:]

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

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

让我们从头开始,探讨这个问题。

假设您有两个列表:

list_1 = ['01', '98']
list_2 = [['01', '98']]

我们必须复制两个列表,现在从第一个列表开始:

因此,首先让我们将变量副本设置为原始列表list_1:

copy = list_1

现在,如果你认为copy复制了list_1,那么你错了。id函数可以告诉我们两个变量是否可以指向同一个对象。让我们试试看:

print(id(copy))
print(id(list_1))

输出为:

4329485320
4329485320

这两个变量是完全相同的参数。你惊讶吗?

所以我们知道,Python不会在变量中存储任何内容,变量只是引用对象,对象存储值。这里的对象是一个列表,但我们通过两个不同的变量名创建了对同一对象的两个引用。这意味着两个变量都指向同一个对象,只是名称不同。

当您执行copy=list_1时,它实际上正在执行以下操作:

在这里,图像list_1和copy是两个变量名,但两个变量的对象是相同的,即列表。

因此,如果您尝试修改复制的列表,那么它也会修改原始列表,因为那里只有一个列表,无论您是从复制的列表还是从原始列表进行修改,都会修改该列表:

copy[0] = "modify"

print(copy)
print(list_1)

输出:

['modify', '98']
['modify', '98']

所以它修改了原始列表:

现在,让我们来看看复制列表的Pythonic方法。

copy_1 = list_1[:]

该方法解决了我们遇到的第一个问题:

print(id(copy_1))
print(id(list_1))

4338792136
4338791432

因此,我们可以看到两个列表都有不同的id,这意味着两个变量都指向不同的对象。所以这里的实际情况是:

现在,让我们尝试修改列表,看看我们是否仍然面临前面的问题:

copy_1[0] = "modify"

print(list_1)
print(copy_1)

输出为:

['01', '98']
['modify', '98']

如您所见,它只修改了复制的列表。这意味着它奏效了。

你认为我们结束了吗?不,让我们尝试复制嵌套列表。

copy_2 = list_2[:]

list2应该引用另一个对象,该对象是list2的副本。让我们检查一下:

print(id((list_2)), id(copy_2))

我们得到输出:

4330403592 4330403528

现在我们可以假设两个列表都指向不同的对象,所以现在让我们尝试修改它,看看它给出了我们想要的:

copy_2[0][1] = "modify"

print(list_2, copy_2)

这为我们提供了输出:

[['01', 'modify']] [['01', 'modify']]

这可能看起来有点令人困惑,因为我们以前使用的相同方法奏效了。让我们试着理解这一点。

当您这样做时:

copy_2 = list_2[:]

你只是在复制外部列表,而不是内部列表。我们可以再次使用id函数来检查这一点。

print(id(copy_2[0]))
print(id(list_2[0]))

输出为:

4329485832
4329485832

当我们执行copy_2=list_2[:]时,会发生以下情况:

它创建列表副本,但仅创建外部列表副本,而不是嵌套列表副本。两个变量的嵌套列表都相同,因此如果您尝试修改嵌套列表,那么它也会修改原始列表,因为嵌套列表对象对于两个列表都相同。

解决方案是什么?解决方案是deepcopy函数。

from copy import deepcopy
deep = deepcopy(list_2)

让我们检查一下:

print(id((list_2)), id(deep))

4322146056 4322148040

两个外部列表都有不同的ID。让我们在内部嵌套列表上尝试一下。

print(id(deep[0]))
print(id(list_2[0]))

输出为:

4322145992
4322145800

正如您所看到的,两个ID都不同,这意味着我们可以假设两个嵌套列表现在都指向不同的对象。

这意味着当您执行deep=deepcopy(list_2)时,实际发生了什么:

两个嵌套列表都指向不同的对象,现在它们有嵌套列表的单独副本。

现在,让我们尝试修改嵌套列表,看看它是否解决了前面的问题:

deep[0][1] = "modify"
print(list_2, deep)

它输出:

[['01', '98']] [['01', 'modify']]

如您所见,它没有修改原始嵌套列表,只修改了复制的列表。

new_list=my_list实际上并没有创建第二个列表。赋值只是将引用复制到列表,而不是实际的列表,因此new_list和my_list在赋值后都引用相同的列表。

要实际复制列表,您有几个选项:

您可以使用内置的list.copy()方法(从Python 3.3开始提供):new_list=old_list.copy()您可以对其进行切片:new_list=旧列表[:]亚历克斯·马特利(Alex Martelli)(至少在2007年)对此的看法是,这是一种奇怪的语法,永远使用它都没有意义(在他看来,下一篇更具可读性)。您可以使用内置的list()构造函数:new_list=列表(old_list)您可以使用泛型copy.copy():导入副本new_list=复制副本(old_list)这比list()慢一点,因为它必须首先找到old_list的数据类型。如果您还需要复制列表中的元素,请使用genericcopy.deepcopy():导入副本new_list=复制.depcopy(old_list)显然是最慢、最需要内存的方法,但有时不可避免。这是递归操作;它将处理任意级别的嵌套列表(或其他容器)。

例子:

import copy

class Foo(object):
    def __init__(self, val):
         self.val = val

    def __repr__(self):
        return f'Foo({self.val!r})'

foo = Foo(1)

a = ['foo', foo]
b = a.copy()
c = a[:]
d = list(a)
e = copy.copy(a)
f = copy.deepcopy(a)

# edit orignal list and instance 
a.append('baz')
foo.val = 5

print(f'original: {a}\nlist.copy(): {b}\nslice: {c}\nlist(): {d}\ncopy: {e}\ndeepcopy: {f}')

结果:

original: ['foo', Foo(5), 'baz']
list.copy(): ['foo', Foo(5)]
slice: ['foo', Foo(5)]
list(): ['foo', Foo(5)]
copy: ['foo', Foo(5)]
deepcopy: ['foo', Foo(1)]