使用new_list=my_list时,对new_list的任何修改都会每次更改my_list。为什么会出现这种情况,以及如何克隆或复制列表以防止出现这种情况?
当前回答
使用对象[:]
>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>>
其他回答
已经有很多答案告诉你如何制作一个正确的副本,但没有一个答案说明为什么你的原始“副本”失败了。
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)
有关复制中的角盒的详细信息,请参阅文档。
deepcopy选项是唯一适用于我的方法:
from copy import deepcopy
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = deepcopy(a)
b[0][1]=[3]
print('Deep:')
print(a)
print(b)
print('-----------------------------')
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = a*1
b[0][1]=[3]
print('*1:')
print(a)
print(b)
print('-----------------------------')
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = a[:]
b[0][1]=[3]
print('Vector copy:')
print(a)
print(b)
print('-----------------------------')
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = list(a)
b[0][1]=[3]
print('List copy:')
print(a)
print(b)
print('-----------------------------')
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = a.copy()
b[0][1]=[3]
print('.copy():')
print(a)
print(b)
print('-----------------------------')
a = [ [ list(range(1, 3)) for i in range(3) ] ]
b = a
b[0][1]=[3]
print('Shallow:')
print(a)
print(b)
print('-----------------------------')
导致输出:
Deep:
[[[1, 2], [1, 2], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
*1:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Vector copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
List copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
.copy():
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Shallow:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [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.copy()或copy.deepcopy(),而不是其他选项,例如在Python 3中:
import copy
class MyList(list):
pass
lst = MyList([1,2,3])
lst.name = 'custom list'
d = {
'original': lst,
'slicecopy' : lst[:],
'lstcopy' : lst.copy(),
'copycopy': copy.copy(lst),
'deepcopy': copy.deepcopy(lst)
}
for k,v in d.items():
print('lst: {}'.format(k), end=', ')
try:
name = v.name
except AttributeError:
name = 'NA'
print('name: {}'.format(name))
输出:
lst: original, name: custom list
lst: slicecopy, name: NA
lst: lstcopy, name: NA
lst: copycopy, name: custom list
lst: deepcopy, name: custom list
让我们从头开始,探讨这个问题。
假设您有两个列表:
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']]
如您所见,它没有修改原始嵌套列表,只修改了复制的列表。
推荐文章
- 证书验证失败:无法获得本地颁发者证书
- 当使用pip3安装包时,“Python中的ssl模块不可用”
- 无法切换Python与pyenv
- Python if not == vs if !=
- 如何从scikit-learn决策树中提取决策规则?
- 为什么在Mac OS X v10.9 (Mavericks)的终端中apt-get功能不起作用?
- 将旋转的xtick标签与各自的xtick对齐
- 为什么元组可以包含可变项?
- 如何合并字典的字典?
- 如何创建类属性?
- 不区分大小写的“in”
- 在Python中获取迭代器中的元素个数
- 解析日期字符串并更改格式
- 使用try和。Python中的if
- 如何在Python中获得所有直接子目录