使用new_list=my_list时,对new_list的任何修改都会每次更改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存在严重的性能问题。
其他回答
让我们从头开始,探讨这个问题。
假设您有两个列表:
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']]
如您所见,它没有修改原始嵌套列表,只修改了复制的列表。
让我惊讶的是,这还没有被提及,所以为了完整起见。。。
您可以使用“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[:]
在Python中,请记住:
list1 = ['apples','bananas','pineapples']
list2 = list1
List2没有存储实际的列表,而是对list1的引用。因此,当您对list1执行任何操作时,list2也会发生变化。使用copy模块(非默认,在pip上下载)制作列表的原始副本(对于简单列表,copy.copy();对于嵌套列表,copy。deepcopy())。这将生成一个不会随第一个列表而更改的副本。
推荐文章
- 证书验证失败:无法获得本地颁发者证书
- 当使用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中获得所有直接子目录