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()
方法不复制类(仅字典/列表/元组) - 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]
(一个列表理解) - 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[:]
(列表切片)
所以最快的是列表切片。但请注意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
这是否意味着追加和列表理解是最好的选择?
我继续回到这个答案,以确保我使用的是最有效的方法。测试这个最简单的方法是什么?或者是否有一个数据库具有所有最好的方法来最小化运行时间?
这些数字可能已经过时了。我试着跑步脚本的修改版本在我的电脑上(甚至很旧),所有的数字都显着降低。值得注意的是,据我计算,列表理解的速度要快 4 倍以上。
有什么用list.copy()
?
@wjandrea 随着硬件的改进,这些数字总是相互关联的,并且有些近似。有趣的是是否比率改变使用新的 Python 版本(还请记住,新的 Python 版本以及标准库升级可能会创建新的完全方法)。
@Karl 好点,他们没有相对改变位置,但比率已经改变。列表推导从比切片慢 5.6 倍到 2.8 倍,相对加速了 200%。
我没有调查过;但我的猜测是 3.x 中对本地查找的更改,除了使列表推导获得自己的范围的更改之外,还可以将迭代变量的查找编译成LOAD_FAST
这会导致相对加速。
我有被告知Python 3.3+添加了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:是的,他们在 3.7 中为 Python 级别的方法调用添加了优化扩展到PEP 590 在 3.8 中调用 C 扩展方法这消除了每次调用方法时创建绑定方法的开销,因此调用成本alist.copy()
现在是一个dict
查找list
类型,然后是一个相对便宜的无参数函数调用,最终调用与切片相同的东西。切片还是要建一个slice
对象,然后通过类型检查和解包来做同样的事情。
当然,他们正在努力优化常量切片的重复构建,所以在 3.10 中切片可能会再次获胜。不过,这一切都毫无意义;渐近性能是相同的,并且固定开销相对较小,因此您使用哪种方法并不重要。
在 Python 中克隆或复制列表的选项有哪些?
在 Python 3 中,可以使用以下方法进行浅拷贝:
a_copy = a_list.copy()
在 Python 2 和 3 中,您可以获得包含原始完整切片的浅拷贝:
a_copy = a_list[:]
解释
复制列表有两种语义方式。浅拷贝创建相同对象的新列表,深拷贝创建包含新等效对象的新列表。
浅表副本
浅拷贝只复制列表本身,它是对列表中对象的引用的容器。如果自身包含的对象是可变的并且其中一个已更改,则更改将反映在两个列表中。
在 Python 2 和 3 中有不同的方法可以做到这一点。Python 2 的方法也适用于 Python 3。
蟒蛇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
蟒蛇 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'], [], []]
该列表只是指向内容的指针数组,因此浅拷贝只是复制指针,因此您有两个不同的列表,但它们具有相同的内容。要复制内容,您需要一个深拷贝。
深拷贝
做一个列表的深层副本,在 Python 2 或 3 中,使用deepcopy
在里面copy
模块:
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))
而且速度要快得多。
让我们从头开始,探讨这个问题。
因此,假设您有两个列表:
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和复制是两个变量名,但两个变量的对象相同,即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']]
如您所见,它没有修改原始嵌套列表,它只修改了复制的列表。
已经有很多答案告诉您如何制作正确的副本,但没有一个回答您的原始“副本”失败的原因。
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)
看文档有关复制中极端情况的更多信息。
new_list = my_list
只是分配名称new_list
对对象my_list
指。见Python 常见问题.
也可以看看:stackoverflow.com/questions/240178
有关的:这个帖子和this