使用new_list=my_list时,对new_list的任何修改都会每次更改my_list。为什么会出现这种情况,以及如何克隆或复制列表以防止出现这种情况?
当前回答
在Python中克隆或复制列表有哪些选项?
在Python 3中,可以使用以下方法制作浅层副本:
a_copy = a_list.copy()
在Python 2和3中,您可以获得一个浅层副本,其中包含原始文件的完整切片:
a_copy = a_list[:]
解释
复制列表有两种语义方法。浅副本创建相同对象的新列表,深副本创建包含新等效对象的新的列表。
浅表副本
浅层副本仅复制列表本身,它是对列表中对象的引用的容器。如果包含的对象本身是可变的,并且其中一个对象发生了更改,则更改将反映在两个列表中。
在Python 2和3中有不同的方法来实现这一点。Python 2的方式也适用于Python 3。
Python 2
在Python 2中,制作列表的简单副本的惯用方法是使用原始列表的完整片段:
a_copy = a_list[:]
您也可以通过列表构造函数传递列表来完成相同的任务,
a_copy = list(a_list)
但是使用构造函数效率较低:
>>> timeit
>>> l = range(20)
>>> min(timeit.repeat(lambda: l[:]))
0.30504298210144043
>>> min(timeit.repeat(lambda: list(l)))
0.40698814392089844
Python 3
在Python 3中,列表获取list.copy方法:
a_copy = a_list.copy()
在Python 3.5中:
>>> import timeit
>>> l = list(range(20))
>>> min(timeit.repeat(lambda: l[:]))
0.38448613602668047
>>> min(timeit.repeat(lambda: list(l)))
0.6309100328944623
>>> min(timeit.repeat(lambda: l.copy()))
0.38122922903858125
生成另一个指针不会生成副本
使用new_list=my_list,然后在每次my_list更改时修改new_list。这是为什么?
mylist只是一个指向内存中实际列表的名称。当你说new_list=my_list时,你不是在复制,只是在添加另一个指向内存中原始列表的名称。当我们复制列表时,也会遇到类似的问题。
>>> l = [[], [], []]
>>> l_copy = l[:]
>>> l_copy
[[], [], []]
>>> l_copy[0].append('foo')
>>> l_copy
[['foo'], [], []]
>>> l
[['foo'], [], []]
列表只是指向内容的指针数组,因此浅层副本只是复制指针,因此您有两个不同的列表,但它们具有相同的内容。要复制内容,您需要一个深度副本。
深度副本
要制作列表的深度副本,在Python 2或3中,请在复制模块中使用deepcopy:
import copy
a_deep_copy = copy.deepcopy(a_list)
要演示这如何允许我们创建新的子列表:
>>> import copy
>>> l
[['foo'], [], []]
>>> l_deep_copy = copy.deepcopy(l)
>>> l_deep_copy[0].pop()
'foo'
>>> l_deep_copy
[[], [], []]
>>> l
[['foo'], [], []]
所以我们看到,深度复制的列表与原始列表完全不同。你可以滚动自己的函数,但不要。通过使用标准库的deepcopy功能,您很可能会创建一些错误。
不使用eval
你可能会看到这是一种深度复制的方式,但不要这样做:
problematic_deep_copy = eval(repr(a_list))
这是很危险的,特别是当你从一个你不信任的来源评估某件事情时。如果要复制的子元素没有一个可以求值以重现等效元素的表示,那么它就不可靠。它的性能也较差。
在64位Python 2.7中:
>>> import timeit
>>> import copy
>>> l = range(10)
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
27.55826997756958
>>> min(timeit.repeat(lambda: eval(repr(l))))
29.04534101486206
在64位Python 3.5上:
>>> import timeit
>>> import copy
>>> l = list(range(10))
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
16.84255409205798
>>> min(timeit.repeat(lambda: eval(repr(l))))
34.813894678023644
其他回答
请注意,在某些情况下,如果您定义了自己的自定义类,并且希望保留这些属性,则应使用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
对每种复制模式的简短解释:
浅层副本构造一个新的复合对象,然后(在可能的范围内)向其中插入对原始对象的引用-创建浅层副本:
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中克隆或复制列表有哪些选项?
在Python 3中,可以使用以下方法制作浅层副本:
a_copy = a_list.copy()
在Python 2和3中,您可以获得一个浅层副本,其中包含原始文件的完整切片:
a_copy = a_list[:]
解释
复制列表有两种语义方法。浅副本创建相同对象的新列表,深副本创建包含新等效对象的新的列表。
浅表副本
浅层副本仅复制列表本身,它是对列表中对象的引用的容器。如果包含的对象本身是可变的,并且其中一个对象发生了更改,则更改将反映在两个列表中。
在Python 2和3中有不同的方法来实现这一点。Python 2的方式也适用于Python 3。
Python 2
在Python 2中,制作列表的简单副本的惯用方法是使用原始列表的完整片段:
a_copy = a_list[:]
您也可以通过列表构造函数传递列表来完成相同的任务,
a_copy = list(a_list)
但是使用构造函数效率较低:
>>> timeit
>>> l = range(20)
>>> min(timeit.repeat(lambda: l[:]))
0.30504298210144043
>>> min(timeit.repeat(lambda: list(l)))
0.40698814392089844
Python 3
在Python 3中,列表获取list.copy方法:
a_copy = a_list.copy()
在Python 3.5中:
>>> import timeit
>>> l = list(range(20))
>>> min(timeit.repeat(lambda: l[:]))
0.38448613602668047
>>> min(timeit.repeat(lambda: list(l)))
0.6309100328944623
>>> min(timeit.repeat(lambda: l.copy()))
0.38122922903858125
生成另一个指针不会生成副本
使用new_list=my_list,然后在每次my_list更改时修改new_list。这是为什么?
mylist只是一个指向内存中实际列表的名称。当你说new_list=my_list时,你不是在复制,只是在添加另一个指向内存中原始列表的名称。当我们复制列表时,也会遇到类似的问题。
>>> l = [[], [], []]
>>> l_copy = l[:]
>>> l_copy
[[], [], []]
>>> l_copy[0].append('foo')
>>> l_copy
[['foo'], [], []]
>>> l
[['foo'], [], []]
列表只是指向内容的指针数组,因此浅层副本只是复制指针,因此您有两个不同的列表,但它们具有相同的内容。要复制内容,您需要一个深度副本。
深度副本
要制作列表的深度副本,在Python 2或3中,请在复制模块中使用deepcopy:
import copy
a_deep_copy = copy.deepcopy(a_list)
要演示这如何允许我们创建新的子列表:
>>> import copy
>>> l
[['foo'], [], []]
>>> l_deep_copy = copy.deepcopy(l)
>>> l_deep_copy[0].pop()
'foo'
>>> l_deep_copy
[[], [], []]
>>> l
[['foo'], [], []]
所以我们看到,深度复制的列表与原始列表完全不同。你可以滚动自己的函数,但不要。通过使用标准库的deepcopy功能,您很可能会创建一些错误。
不使用eval
你可能会看到这是一种深度复制的方式,但不要这样做:
problematic_deep_copy = eval(repr(a_list))
这是很危险的,特别是当你从一个你不信任的来源评估某件事情时。如果要复制的子元素没有一个可以求值以重现等效元素的表示,那么它就不可靠。它的性能也较差。
在64位Python 2.7中:
>>> import timeit
>>> import copy
>>> l = range(10)
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
27.55826997756958
>>> min(timeit.repeat(lambda: eval(repr(l))))
29.04534101486206
在64位Python 3.5上:
>>> import timeit
>>> import copy
>>> l = list(range(10))
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
16.84255409205798
>>> min(timeit.repeat(lambda: eval(repr(l))))
34.813894678023644
还有另一种方法可以复制一个直到现在才列出的列表:添加一个空列表:l2=l+[]。
我用Python 3.8测试了它:
l = [1,2,3]
l2 = l + []
print(l,l2)
l[0] = 'a'
print(l,l2)
这不是最好的答案,但它奏效了。
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]]]
-----------------------------
推荐文章
- 证书验证失败:无法获得本地颁发者证书
- 当使用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中获得所有直接子目录