如何克隆一个列表以避免赋值后意外改变?
当你使用 new_list = my_list
时,每次你对 new_list
进行修改,my_list
也会跟着改变。这是为什么呢?其实这是因为 new_list
和 my_list
指向的是同一个列表。也就是说,它们共享同一块内存空间。为了避免这种情况,你需要克隆或者复制这个列表,这样就可以让 new_list
和 my_list
各自独立,互不影响。
24 个回答
我听说Python 3.3及以上版本增加了一个叫做 list.copy()
的方法,这个方法的速度应该和用切片一样快:
newlist = old_list.copy()
Felix已经给出了很好的答案,但我想对各种方法的速度做个比较:
- 10.59秒(每次105.9微秒) -
copy.deepcopy(old_list)
- 10.16秒(每次101.6微秒) - 纯Python的
Copy()
方法,使用深拷贝复制类 - 1.488秒(每次14.88微秒) - 纯Python的
Copy()
方法,不复制类(只复制字典/列表/元组) - 0.325秒(每次3.25微秒) -
for item in old_list: new_list.append(item)
- 0.217秒(每次2.17微秒) -
[i for i in old_list]
(这是一种列表推导式) - 0.186秒(每次1.86微秒) -
copy.copy(old_list)
- 0.075秒(每次0.75微秒) -
list(old_list)
- 0.053秒(每次0.53微秒) -
new_list = []; new_list.extend(old_list)
- 0.039秒(每次0.39微秒) -
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
new_list = my_list
实际上并没有创建一个新的列表。这个赋值只是复制了对列表的引用,而不是实际的列表内容,所以在赋值后,new_list
和 my_list
都指向同一个列表。
如果你想真正复制一个列表,有几种方法可以选择:
你可以使用内置的
list.copy()
方法(从 Python 3.3 开始可用):new_list = old_list.copy()
你可以用切片来复制:
new_list = old_list[:]
Alex Martelli 的看法(至少在 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 f'Foo({self.val!r})'
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(f'original: {a}\nlist.copy(): {b}\nslice: {c}\nlist(): {d}\ncopy: {e}\ndeepcopy: {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)]