分配后列表意外更改。为啥会这样,我该如何预防?
Posted
技术标签:
【中文标题】分配后列表意外更改。为啥会这样,我该如何预防?【英文标题】:List changes unexpectedly after assignment. Why is this and how can I prevent it?分配后列表意外更改。为什么会这样,我该如何预防? 【发布时间】:2021-11-20 18:14:08 【问题描述】:使用new_list = my_list
时,对new_list
的任何修改每次都会更改my_list
。为什么会这样,如何克隆或复制列表以防止它发生?
【问题讨论】:
new_list = my_list
只是将名称 new_list
分配给 my_list
所指的对象。
见Python FAQ。
【参考方案1】:
使用new_list = my_list
,您实际上没有两个列表。赋值只是复制对列表的引用,而不是实际的列表,所以new_list
和my_list
在赋值后引用同一个列表。
要实际复制列表,您有多种可能性:
您可以使用内置的list.copy()
方法(Python 3.3 起可用):
new_list = old_list.copy()
你可以切片:
new_list = old_list[:]
Alex Martelli's 意见(至少back in 2007)对此的看法是,这是一种奇怪的语法,永远使用它没有意义。 ;)(在他看来,下一个更具可读性)。
你可以使用内置的list()
函数:
new_list = list(old_list)
你可以使用通用的copy.copy()
:
import copy
new_list = copy.copy(old_list)
这比list()
慢一点,因为它必须先找出old_list
的数据类型。
如果列表包含对象并且您也想复制它们,请使用通用copy.deepcopy()
:
import copy
new_list = copy.deepcopy(old_list)
显然是最慢且最需要内存的方法,但有时是不可避免的。
示例:
import copy
class Foo(object):
def __init__(self, val):
self.val = val
def __repr__(self):
return 'Foo(!r)'.format(self.val)
foo = Foo(1)
a = ['foo', foo]
b = a.copy()
c = a[:]
d = list(a)
e = copy.copy(a)
f = copy.deepcopy(a)
# edit orignal list and instance
a.append('baz')
foo.val = 5
print('original: %r\nlist.copy(): %r\nslice: %r\nlist(): %r\ncopy: %r\ndeepcopy: %r'
% (a, b, c, d, e, f))
结果:
original: ['foo', Foo(5), 'baz']
list.copy(): ['foo', Foo(5)]
slice: ['foo', Foo(5)]
list(): ['foo', Foo(5)]
copy: ['foo', Foo(5)]
deepcopy: ['foo', Foo(1)]
【讨论】:
正如@Georgy 在下面的答案中正确指出的那样,对 new_list 值的任何更改也会更改 my_list 中的值。所以实际上 copy.deepcopy() 方法是唯一没有参考原始列表及其值的真实副本。 你说得对,它是由你编辑的,但由 @cryo 发布 抱歉混淆了! 哪个最快? 我在 json 列表中遇到了同样的问题(列表的每个元素都是一个 json),唯一有效的是 new_list = copy.deepcopy(old_list) ;我写这篇文章是因为任何人都可能遇到同样的问题。谢谢! +1 用于切片[:]
这是一种简单而紧凑的语法,每次需要复制列表时都使用它确实有意义,并且可以避免 deepcopy
【参考方案2】:
Felix 已经提供了一个很好的答案,但我想我应该对各种方法进行速度比较:
-
10.59 秒 (105.9 µs/itn) -
copy.deepcopy(old_list)
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) - for item in old_list: new_list.append(item)
0.217 秒 (2.17 µs/itn) - [i for i in old_list]
(a list comprehension)
0.186 秒 (1.86 µs/itn) - copy.copy(old_list)
0.075 秒 (0.75 µs/itn) - list(old_list)
0.053 秒 (0.53 µs/itn) - new_list = []; new_list.extend(old_list)
0.039 秒 (0.39 µs/itn) - old_list[:]
(list slicing)
所以最快的是列表切片。但请注意copy.copy()
、list[:]
和list(list)
,与copy.deepcopy()
不同,python 版本不会复制列表中的任何列表、字典和类实例,因此如果原件发生变化,它们会在也复制列表,反之亦然。
(如果有人有兴趣或想提出任何问题,这里是脚本:)
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
【讨论】:
这是否意味着追加和列表理解是最好的选择? 我有一个包含类列表的缓存,我想获取锁,复制列表,释放锁。我希望使用内置副本来保护复制出的列表在缓存副本更改时不会更改。 我会继续返回这个答案,以确保我使用的是最有效的方法。测试这个的最简单方法是什么?或者是否有一个数据库可以最大限度地减少运行时间?【参考方案3】:我有 been told 那个 Python 3.3+ adds the list.copy()
方法,应该和切片一样快:
newlist = old_list.copy()
【讨论】:
是的,根据文档docs.python.org/3/library/stdtypes.html#mutable-sequence-types,s.copy()
创建了s
的浅表副本(与s[:]
相同)。
实际上,目前看来,python3.8
、.copy()
比切片略快。请参阅下面的@AaronsHall 答案。
@loved.by.Jesus: 是的,他们added optimizations for Python level method calls in 3.7 被扩展为C extension method calls in 3.8 by PEP 590,消除了每次调用方法时创建绑定方法的开销,因此调用@ 的成本987654334@ 现在是 dict
对 list
类型的查找,然后是一个相对便宜的无参数函数调用,最终调用与切片相同的东西。切片仍然需要构建一个slice
对象,然后通过类型检查和解包来做同样的事情。
当然,他们正在开发optimizing out the repeated builds of constant slices,所以在 3.10 中切片可能会再次获胜。不过,这一切都毫无意义;渐近性能是相同的,并且固定开销相对较小,因此您使用哪种方法并不重要。【参考方案4】:
在 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。这是为什么呢?
my_list
只是一个指向内存中实际列表的名称。当您说new_list = my_list
时,您不是在制作副本,您只是在添加另一个名称,该名称指向内存中的原始列表。当我们复制列表时,我们可能会遇到类似的问题。
>>> l = [[], [], []]
>>> l_copy = l[:]
>>> l_copy
[[], [], []]
>>> l_copy[0].append('foo')
>>> l_copy
[['foo'], [], []]
>>> l
[['foo'], [], []]
列表只是一个指向内容的指针数组,因此浅拷贝只是复制指针,因此您有两个不同的列表,但它们具有相同的内容。要复制内容,您需要深拷贝。
深拷贝
制作deep copy of a list, in Python 2 or 3, use deepcopy
in the copy
module:
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
【讨论】:
如果列表是 2D,则不需要 deepcopy。如果它是列表列表,并且这些列表中没有列表,则可以使用 for 循环。目前,我使用的是list_copy=[]
for item in list: list_copy.append(copy(item))
,速度更快。【参考方案5】:
已经有很多答案告诉您如何制作正确的副本,但没有一个回答说明您的原始“副本”失败的原因。
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)
请参阅the documentation,了解有关复制中极端情况的更多信息。
【讨论】:
在处理这个话题时,我应该向下滚动到您的答案。我很难弄清楚为什么我的“列表列表”副本实际上不是副本...... :-) 谢谢这个!【参考方案6】:让我们从头开始,探索这个问题。
假设您有两个列表:
list_1 = ['01', '98']
list_2 = [['01', '98']]
我们必须复制两个列表,现在从第一个列表开始:
首先让我们尝试将变量copy
设置为我们的原始列表list_1
:
copy = list_1
现在如果你想复制 list_1,那你就错了。 id
函数可以告诉我们两个变量是否可以指向同一个对象。让我们试试这个:
print(id(copy))
print(id(list_1))
输出是:
4329485320
4329485320
两个变量是完全相同的参数。你感到惊讶吗?
所以我们知道,Python 不会在变量中存储任何内容,变量只是引用对象,而对象存储值。这里的对象是list
,但我们通过两个不同的变量名创建了对同一个对象的两个引用。这意味着两个变量都指向同一个对象,只是名称不同。
当你做copy = list_1
时,它实际上是在做:
在图像中 list_1 和 copy 是两个变量名,但两个变量的对象相同,即 list
。
因此,如果您尝试修改复制的列表,那么它也会修改原始列表,因为该列表只有一个,无论您是从复制的列表还是从原始列表进行修改,您都将修改该列表:
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[:]
list_2
应该引用另一个对象,它是list_2
的副本。让我们检查一下:
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']]
如你所见,它并没有修改原来的嵌套列表,它只是修改了复制的列表。
【讨论】:
【参考方案7】:使用thing[:]
>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>>
【讨论】:
【参考方案8】:Python 3.6 时序
以下是使用 Python 3.6.8 的计时结果。请记住,这些时间是相对的,而不是绝对的。
我坚持只做浅拷贝,还添加了一些在 Python 2 中无法实现的新方法,例如list.copy()
(Python 3 slice equivalent)和两种形式的list unpacking(*new_list, = list
和new_list = [*list]
):
METHOD TIME TAKEN
b = [*a] 2.75180600000021
b = a * 1 3.50215399999990
b = a[:] 3.78278899999986 # Python 2 winner (see above)
b = a.copy() 4.20556500000020 # Python 3 "slice equivalent" (see above)
b = []; b.extend(a) 4.68069800000012
b = a[0:len(a)] 6.84498999999959
*b, = a 7.54031799999984
b = list(a) 7.75815899999997
b = [i for i in a] 18.4886440000000
b = copy.copy(a) 18.8254879999999
b = []
for item in a:
b.append(item) 35.4729199999997
我们可以看到 Python 2 的获胜者仍然表现出色,但与 Python 3 list.copy()
相比并没有太大优势,尤其是考虑到后者的卓越可读性。
黑马是解包和重新打包方法 (b = [*a]
),比原始切片快约 25%,比其他解包方法 (*b, = a
) 快两倍以上。
b = a * 1
的表现也出奇的好。
请注意,这些方法不为列表以外的任何输入输出等效结果。它们都适用于可切片对象,少数适用于任何可迭代对象,但仅适用于 @ 987654333@ 适用于更通用的 Python 对象。
这里是感兴趣的人的测试代码(Template from here):
import timeit
COUNT = 50000000
print("Array duplicating. Tests run", COUNT, "times")
setup = 'a = [0,1,2,3,4,5,6,7,8,9]; import copy'
print("b = list(a)\t\t", timeit.timeit(stmt='b = list(a)', setup=setup, number=COUNT))
print("b = copy.copy(a)\t", timeit.timeit(stmt='b = copy.copy(a)', setup=setup, number=COUNT))
print("b = a.copy()\t\t", timeit.timeit(stmt='b = a.copy()', setup=setup, number=COUNT))
print("b = a[:]\t\t", timeit.timeit(stmt='b = a[:]', setup=setup, number=COUNT))
print("b = a[0:len(a)]\t\t", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\t\t\t", timeit.timeit(stmt='*b, = a', setup=setup, number=COUNT))
print("b = []; b.extend(a)\t", timeit.timeit(stmt='b = []; b.extend(a)', setup=setup, number=COUNT))
print("b = []; for item in a: b.append(item)\t", timeit.timeit(stmt='b = []\nfor item in a: b.append(item)', setup=setup, number=COUNT))
print("b = [i for i in a]\t", timeit.timeit(stmt='b = [i for i in a]', setup=setup, number=COUNT))
print("b = [*a]\t\t", timeit.timeit(stmt='b = [*a]', setup=setup, number=COUNT))
print("b = a * 1\t\t", timeit.timeit(stmt='b = a * 1', setup=setup, number=COUNT))
【讨论】:
可以在 3.8b=[*a]
上确认类似的故事——这是一种显而易见的方法;)。
在复制如此小的列表时,其中一些时间比较并不是特别有意义。使用一系列列表长度(包括一些非常大的长度)进行测试会提供更多信息。
计时数字应该四舍五入到适当的有效数字位数。 15 位有效数字没有任何意义。
我基本上只是在此处粘贴了时序代码的原始输出。似乎您的抱怨更多是关于 timeit 如何显示时间,而我几乎无法控制。【参考方案9】:
Python 的习惯用法是newList = oldList[:]
【讨论】:
【参考方案10】:所有其他贡献者都给出了很好的答案,当你有一个单一的维度(水平)列表时,它可以工作,但是到目前为止提到的方法中,只有 copy.deepcopy()
可以克隆/复制一个当您使用多维嵌套列表(列表列表)时,列表而不是让它指向嵌套的 list
对象。虽然Felix Kling 在他的回答中提到了它,但这个问题还有一点点,可能还有一个使用内置插件的解决方法,这可能证明是deepcopy
的更快替代方案。
虽然 new_list = old_list[:]
、copy.copy(old_list)'
和 Py3k 的 old_list.copy()
适用于单级列表,但它们恢复为指向嵌套在 old_list
和 new_list
中的 list
对象,并更改为一个的list
对象在另一个对象中永久存在。
编辑:新信息曝光
正如Aaron Hall 和PM 2Ring 都指出的那样使用
eval()
不仅是一个坏主意,而且比copy.deepcopy()
慢得多。这意味着对于多维列表,唯一的选项是
copy.deepcopy()
。话虽如此,当您尝试在中等大小的多维数组上使用它时,性能会下降,因此它确实不是一个选择。我尝试使用 42x42 数组来timeit
,这对于生物信息学应用程序来说不是闻所未闻甚至那么大,但我放弃了等待回复并开始输入我对这篇文章的编辑。似乎唯一真正的选择是初始化多个列表并独立处理它们。如果有人对如何处理多维列表复制有任何其他建议,将不胜感激。
正如其他人所说,使用copy
模块和copy.deepcopy
存在严重的性能问题多维列表 .
【讨论】:
这并不总是有效,因为不能保证repr()
返回的字符串足以重新创建对象。此外,eval()
是不得已的工具;有关详细信息,请参阅 SO 老将 Ned Batchelder 的Eval really is dangerous。所以当你提倡使用eval()
时,你真的应该提到它可能很危险。
公平点。尽管我认为 Batchelder 的观点是,在 Python 中使用 eval()
函数通常是有风险的。与其说你是否在代码中使用该函数,不如说它本身就是 Python 中的一个安全漏洞。我的示例没有将它与从input()
、sys.agrv
甚至文本文件接收输入的函数一起使用。它更像是初始化一个空白的多维列表一次,然后在循环中复制它,而不是在循环的每次迭代中重新初始化。
正如@AaronHall 所指出的,使用new_list = eval(repr(old_list))
可能存在严重的性能问题,所以除了这是一个坏主意之外,它可能工作起来也太慢了。【参考方案11】:
令我惊讶的是,这还没有被提及,所以为了完整起见......
您可以使用“splat 运算符”执行列表解包:*
,这也将复制您的列表元素。
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)
【讨论】:
这个方法在修改副本时表现如何? @not2qubit 你的意思是追加或编辑新列表的元素。在示例中old_list
和new_list
是两个不同的列表,编辑一个不会更改另一个(除非您直接改变元素本身(例如列表列表),这些方法都不是深拷贝)。 【参考方案12】:
已经给出的答案中缺少一种独立于 python 版本的非常简单的方法,您可以在大多数时间使用(至少我这样做):
new_list = my_list * 1 # Solution 1 when you are not using nested lists
但是,if 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
【讨论】:
【参考方案13】:我想发布一些与其他答案有些不同的东西。尽管这很可能不是最容易理解或最快的选项,但它提供了一些关于深度复制如何工作的内部视图,并且是深度复制的另一种替代选择。我的函数是否有错误并不重要,因为这样做的目的是展示一种复制对象(如问题答案)的方法,同时也以此为重点来解释 deepcopy 的核心工作原理。
任何深拷贝功能的核心都是制作浅拷贝的方法。如何?简单的。任何深拷贝函数只复制不可变对象的容器。当您对嵌套列表进行深度复制时,您只是在复制外部列表,而不是列表内的可变对象。您只是在复制容器。这同样适用于类。当你深拷贝一个类时,你深拷贝了它的所有可变属性。又怎样?为什么只需要复制容器,例如列表、字典、元组、迭代器、类和类实例?
这很简单。可变对象不能真正被复制。它永远无法更改,因此它只是一个值。这意味着您永远不必复制字符串、数字、布尔值或任何这些。但是你将如何复制容器?简单的。您只需使用所有值初始化一个新容器。 Deepcopy 依赖于递归。它会复制所有容器,甚至是其中包含容器的容器,直到没有容器留下。容器是不可变的对象。
一旦你知道了,完全复制一个没有任何引用的对象就很容易了。这是一个用于深度复制基本数据类型的函数(不适用于自定义类,但您可以随时添加)
def deepcopy(x):
immutables = (str, int, bool, float)
mutables = (list, dict, tuple)
if isinstance(x, immutables):
return x
elif isinstance(x, mutables):
if isinstance(x, tuple):
return tuple(deepcopy(list(x)))
elif isinstance(x, list):
return [deepcopy(y) for y in x]
elif isinstance(x, dict):
values = [deepcopy(y) for y in list(x.values())]
keys = list(x.keys())
return dict(zip(keys, values))
Python 自己的内置 deepcopy 就是基于该示例。唯一的区别是它支持其他类型,并且还通过将属性复制到新的重复类中来支持用户类,并且还通过对已使用备忘录列表或字典看到的对象的引用来阻止无限递归。这就是制作深拷贝的真正目的。从本质上讲,制作深拷贝只是制作浅拷贝。我希望这个答案能对这个问题有所帮助。
示例
假设你有这个列表:[1, 2, 3]
。不可变的数字不能复制,但另一层可以。您可以使用列表理解来复制它:[x for x in [1, 2, 3]]
现在,假设您有这个列表:[[1, 2], [3, 4], [5, 6]]
。这一次,你想创建一个函数,它使用递归来深度复制列表的所有层。而不是之前的列表理解:
[x for x in _list]
它使用一个新的列表:
[deepcopy_list(x) for x in _list]
而 deepcopy_list 看起来像这样:
def deepcopy_list(x):
if isinstance(x, (str, bool, float, int)):
return x
else:
return [deepcopy_list(y) for y in x]
那么现在你有了一个函数,它可以使用递归将任何 strs、bools、floast、ints 甚至 lists 列表深度复制到无限多个层。有了它,深度复制。
TLDR:Deepcopy 使用递归来复制对象,并且只返回与以前相同的不可变对象,因为不可变对象无法复制。但是,它会深度复制可变对象的最内层,直到到达对象的最外层。
【讨论】:
【参考方案14】:请注意,在某些情况下,如果您定义了自己的自定义类并且想要保留属性,那么您应该使用 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
【讨论】:
【参考方案15】:new_list = my_list[:]
new_list = my_list
试着理解这一点。假设 my_list 在位置 X 的堆内存中,即 my_list 指向 X。现在通过分配 new_list = my_list
你让 new_list 指向 X。这称为 浅拷贝。
现在,如果您分配new_list = my_list[:]
,您只是将my_list 的每个对象复制到new_list。这称为深拷贝。
其他您可以这样做的方法是:
new_list = list(old_list)
import copy
new_list = copy.deepcopy(old_list)
【讨论】:
【参考方案16】:请记住在 Python 中这样做:
list1 = ['apples','bananas','pineapples']
list2 = list1
List2 不是存储实际列表,而是对 list1 的引用。因此,当您对 list1 执行任何操作时,list2 也会发生变化。使用复制模块(非默认,在 pip 上下载)制作列表的原始副本(copy.copy()
用于简单列表,copy.deepcopy()
用于嵌套列表)。这会生成一个不会随第一个列表更改的副本。
【讨论】:
【参考方案17】:通过 id 和 gc 来查看内存的一点实用视角。
>>> b = a = ['hell', 'word']
>>> c = ['hell', 'word']
>>> id(a), id(b), id(c)
(4424020872, 4424020872, 4423979272)
| |
-----------
>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # all referring to same 'hell'
| | |
-----------------------
>>> id(a[0][0]), id(b[0][0]), id(c[0][0])
(4422785208, 4422785208, 4422785208) # all referring to same 'h'
| | |
-----------------------
>>> a[0] += 'o'
>>> a,b,c
(['hello', 'word'], ['hello', 'word'], ['hell', 'word']) # b changed too
>>> id(a[0]), id(b[0]), id(c[0])
(4424018384, 4424018384, 4424018328) # augmented assignment changed a[0],b[0]
| |
-----------
>>> b = a = ['hell', 'word']
>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # the same hell
| | |
-----------------------
>>> import gc
>>> gc.get_referrers(a[0])
[['hell', 'word'], ['hell', 'word']] # one copy belong to a,b, the another for c
>>> gc.get_referrers(('hell'))
[['hell', 'word'], ['hell', 'word'], ('hell', None)] # ('hello', None)
【讨论】:
【参考方案18】: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]]]
-----------------------------
【讨论】:
deepcopy 必须仅在需要时使用,并且应该知道它的真正作用。【参考方案19】:这是因为,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)
【讨论】:
【参考方案20】:使用的方法取决于要复制的列表的内容。如果列表包含嵌套的dicts
,则 deepcopy 是唯一有效的方法,否则答案中列出的大多数方法(切片、循环 [for]、复制、扩展、组合或解包)将在相似的时间内工作和执行(循环和深度复制除外,它们表现最差)。
脚本
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
]]
【讨论】:
【参考方案21】:还有另一种方法可以复制直到现在还没有列出的列表:添加一个空列表:l2 = l + []
。
我用 Python 3.8 对其进行了测试:
l = [1,2,3]
l2 = l + []
print(l,l2)
l[0] = 'a'
print(l,l2)
这不是最好的答案,但它有效。
【讨论】:
【参考方案22】:有一种简单的技术可以解决这个问题。
代码:
number=[1,2,3,4,5,6] #Original list
another=[] #another empty list
for a in number: #here I am declaring variable (a) as an item in the list (number)
another.append(a) #here we are adding the items of list (number) to list (another)
print(another)
输出:
>>> [1,2,3,4,5,6]
我希望这对您的查询有用。
【讨论】:
以上是关于分配后列表意外更改。为啥会这样,我该如何预防?的主要内容,如果未能解决你的问题,请参考以下文章
iOS 14 后列表背景颜色更改并添加了填充,我该如何处理?
窗口“捕获”应用程序在意外终止时允许捕获的窗口消失,我该如何预防/解决此问题?