使用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)
这不是最好的答案,但它奏效了。
其他回答
有人告诉我Python 3.3+添加了list.copy()方法,它应该和切片一样快:
newlist = old_list.copy()
要使用的方法取决于要复制的列表的内容。如果列表中包含嵌套的dict,则deepcopy是唯一有效的方法,否则答案中列出的大多数方法(slice、loop[for]、copy、extend、combine或unpack)都将在类似的时间内工作和执行(loop和deepcopy除外,这两种方法执行得最差)。
剧本
from random import randint
from time import time
import copy
item_count = 100000
def copy_type(l1: list, l2: list):
if l1 == l2:
return 'shallow'
return 'deep'
def run_time(start, end):
run = end - start
return int(run * 1000000)
def list_combine(data):
l1 = [data for i in range(item_count)]
start = time()
l2 = [] + l1
end = time()
if type(data) == dict:
l2[0]['test'].append(1)
elif type(data) == list:
l2.append(1)
return {'method': 'combine', 'copy_type': copy_type(l1, l2),
'time_µs': run_time(start, end)}
def list_extend(data):
l1 = [data for i in range(item_count)]
start = time()
l2 = []
l2.extend(l1)
end = time()
if type(data) == dict:
l2[0]['test'].append(1)
elif type(data) == list:
l2.append(1)
return {'method': 'extend', 'copy_type': copy_type(l1, l2),
'time_µs': run_time(start, end)}
def list_unpack(data):
l1 = [data for i in range(item_count)]
start = time()
l2 = [*l1]
end = time()
if type(data) == dict:
l2[0]['test'].append(1)
elif type(data) == list:
l2.append(1)
return {'method': 'unpack', 'copy_type': copy_type(l1, l2),
'time_µs': run_time(start, end)}
def list_deepcopy(data):
l1 = [data for i in range(item_count)]
start = time()
l2 = copy.deepcopy(l1)
end = time()
if type(data) == dict:
l2[0]['test'].append(1)
elif type(data) == list:
l2.append(1)
return {'method': 'deepcopy', 'copy_type': copy_type(l1, l2),
'time_µs': run_time(start, end)}
def list_copy(data):
l1 = [data for i in range(item_count)]
start = time()
l2 = list.copy(l1)
end = time()
if type(data) == dict:
l2[0]['test'].append(1)
elif type(data) == list:
l2.append(1)
return {'method': 'copy', 'copy_type': copy_type(l1, l2),
'time_µs': run_time(start, end)}
def list_slice(data):
l1 = [data for i in range(item_count)]
start = time()
l2 = l1[:]
end = time()
if type(data) == dict:
l2[0]['test'].append(1)
elif type(data) == list:
l2.append(1)
return {'method': 'slice', 'copy_type': copy_type(l1, l2),
'time_µs': run_time(start, end)}
def list_loop(data):
l1 = [data for i in range(item_count)]
start = time()
l2 = []
for i in range(len(l1)):
l2.append(l1[i])
end = time()
if type(data) == dict:
l2[0]['test'].append(1)
elif type(data) == list:
l2.append(1)
return {'method': 'loop', 'copy_type': copy_type(l1, l2),
'time_µs': run_time(start, end)}
def list_list(data):
l1 = [data for i in range(item_count)]
start = time()
l2 = list(l1)
end = time()
if type(data) == dict:
l2[0]['test'].append(1)
elif type(data) == list:
l2.append(1)
return {'method': 'list()', 'copy_type': copy_type(l1, l2),
'time_µs': run_time(start, end)}
if __name__ == '__main__':
list_type = [{'list[dict]': {'test': [1, 1]}},
{'list[list]': [1, 1]}]
store = []
for data in list_type:
key = list(data.keys())[0]
store.append({key: [list_unpack(data[key]), list_extend(data[key]),
list_combine(data[key]), list_deepcopy(data[key]),
list_copy(data[key]), list_slice(data[key]),
list_loop(data[key])]})
print(store)
后果
[{"list[dict]": [
{"method": "unpack", "copy_type": "shallow", "time_µs": 56149},
{"method": "extend", "copy_type": "shallow", "time_µs": 52991},
{"method": "combine", "copy_type": "shallow", "time_µs": 53726},
{"method": "deepcopy", "copy_type": "deep", "time_µs": 2702616},
{"method": "copy", "copy_type": "shallow", "time_µs": 52204},
{"method": "slice", "copy_type": "shallow", "time_µs": 52223},
{"method": "loop", "copy_type": "shallow", "time_µs": 836928}]},
{"list[list]": [
{"method": "unpack", "copy_type": "deep", "time_µs": 52313},
{"method": "extend", "copy_type": "deep", "time_µs": 52550},
{"method": "combine", "copy_type": "deep", "time_µs": 53203},
{"method": "deepcopy", "copy_type": "deep", "time_µs": 2608560},
{"method": "copy", "copy_type": "deep", "time_µs": 53210},
{"method": "slice", "copy_type": "deep", "time_µs": 52937},
{"method": "loop", "copy_type": "deep", "time_µs": 834774}
]}]
对每种复制模式的简短解释:
浅层副本构造一个新的复合对象,然后(在可能的范围内)向其中插入对原始对象的引用-创建浅层副本:
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版本的非常简单的方法,您可以在大多数时间使用(至少我这样做):
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
让我们从头开始,探讨这个问题。
假设您有两个列表:
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中获得所有直接子目录