使用new_list=my_list时,对new_list的任何修改都会每次更改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
其他回答
已经有很多答案告诉你如何制作一个正确的副本,但没有一个答案说明为什么你的原始“副本”失败了。
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)
有关复制中的角盒的详细信息,请参阅文档。
菲利克斯已经给出了一个很好的答案,但我想我应该对各种方法进行速度比较:
10.59秒(105.9µs/itn)-copy.depcopy(旧列表)10.16秒(101.6µs/itn)-纯Python Copy()方法使用deepcopy复制类1.488秒(14.88µs/itn)-纯Python Copy()方法不复制类(仅dicts/lists/tuples)0.325秒(3.25µs/itn)-对于old_list:new_list.append(项目)中的项目0.217秒(2.17µs/itn)-[i代表old_list](列表理解)0.186秒(1.86µs/itn)-复制副本(old_list)0.075秒(0.75µs/itn)-列表(旧列表)0.053秒(0.53µs/itn)-新列表=[];新列表扩展(旧列表)0.039秒(0.39µs/itn)-old_list[:](列表切片)
所以最快的是列表切片。但请注意,与copy.deepcopy()和python版本不同,copy.copy()、list[:]和list(list)不会复制列表中的任何列表、字典和类实例,因此如果原始列表发生变化,它们也会在复制的列表中发生变化,反之亦然。
(如果有人感兴趣或想提出任何问题,以下是脚本:)
from copy import deepcopy
class old_class:
def __init__(self):
self.blah = 'blah'
class new_class(object):
def __init__(self):
self.blah = 'blah'
dignore = {str: None, unicode: None, int: None, type(None): None}
def Copy(obj, use_deepcopy=True):
t = type(obj)
if t in (list, tuple):
if t == tuple:
# Convert to a list if a tuple to
# allow assigning to when copying
is_tuple = True
obj = list(obj)
else:
# Otherwise just do a quick slice copy
obj = obj[:]
is_tuple = False
# Copy each item recursively
for x in xrange(len(obj)):
if type(obj[x]) in dignore:
continue
obj[x] = Copy(obj[x], use_deepcopy)
if is_tuple:
# Convert back into a tuple again
obj = tuple(obj)
elif t == dict:
# Use the fast shallow dict copy() method and copy any
# values which aren't immutable (like lists, dicts etc)
obj = obj.copy()
for k in obj:
if type(obj[k]) in dignore:
continue
obj[k] = Copy(obj[k], use_deepcopy)
elif t in dignore:
# Numeric or string/unicode?
# It's immutable, so ignore it!
pass
elif use_deepcopy:
obj = deepcopy(obj)
return obj
if __name__ == '__main__':
import copy
from time import time
num_times = 100000
L = [None, 'blah', 1, 543.4532,
['foo'], ('bar',), {'blah': 'blah'},
old_class(), new_class()]
t = time()
for i in xrange(num_times):
Copy(L)
print 'Custom Copy:', time()-t
t = time()
for i in xrange(num_times):
Copy(L, use_deepcopy=False)
print 'Custom Copy Only Copying Lists/Tuples/Dicts (no classes):', time()-t
t = time()
for i in xrange(num_times):
copy.copy(L)
print 'copy.copy:', time()-t
t = time()
for i in xrange(num_times):
copy.deepcopy(L)
print 'copy.deepcopy:', time()-t
t = time()
for i in xrange(num_times):
L[:]
print 'list slicing [:]:', time()-t
t = time()
for i in xrange(num_times):
list(L)
print 'list(L):', time()-t
t = time()
for i in xrange(num_times):
[i for i in L]
print 'list expression(L):', time()-t
t = time()
for i in xrange(num_times):
a = []
a.extend(L)
print 'list extend:', time()-t
t = time()
for i in xrange(num_times):
a = []
for y in L:
a.append(y)
print 'list append:', time()-t
t = time()
for i in xrange(num_times):
a = []
a.extend(i for i in L)
print 'generator expression extend:', time()-t
让我们从头开始,探讨这个问题。
假设您有两个列表:
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']]
如您所见,它没有修改原始嵌套列表,只修改了复制的列表。
在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
new_list = my_list[:]
new_list=我的列表
试着理解这一点。假设my_list位于堆内存中的位置X,即my_list指向X。现在,通过指定new_list=my_list,可以让new_list指向X。这就是所谓的浅拷贝。
现在,如果指定new_list=my_list[:],则只需将my_list的每个对象复制到new_list。这就是所谓的深度复制。
您可以通过以下其他方式完成此操作:
new_list=列表(old_list)导入副本new_list=复制.depcopy(old_list)
推荐文章
- 如何从外部访问本地Django web服务器
- 删除字符串的最后3个字符
- 在python中执行no-op的标准方法是什么?
- 如何从生成器构建numpy数组?
- 什么时候我应该(不)想要在我的代码中使用熊猫apply() ?
- 数据类vs类型。NamedTuple主要用例
- 如何从macOS完全卸载蟒蛇
- 是否有可能键入提示一个lambda函数?
- 'dict'对象没有has_key属性
- 使用Pandas groupby连接来自几行的字符串
- Pandas:给定列的数据帧行之和
- 如何避免在为Python项目构建Docker映像时重新安装包?
- 如何激活蟒蛇环境
- 省略[…]意思是在一个列表里?
- 为什么我得到“'str'对象没有属性'读取'”当尝试使用' json。载入字符串?