python列表拷贝

python里面如果有个列表mylist,想获取它的一个副本或者说拷贝,直接用newlist=mylist是获取不到的,它俩其实是指向同一个对象,修改mylist的内容也会修改到newlist:

>>> mylist=[1,2,3,4] 
>>> newlist=mylist 
>>> id(newlist) 
139984954853960 
>>> id(mylist) 
139984954853960 
>>> mylist[0]=2 
>>> newlist 
[2, 2, 3, 4] 
>>> 

那么python里面如何获取一个列表的副本呢

其实方法还是挺多的,常用的有:

new_list = old_list.copy() 

new_list = old_list[:] 

new_list = list(old_list) 

import copy 
new_list = copy.copy(old_list) 

import copy 
new_list = copy.deepcopy(old_list) 

这里需要注意的是深拷贝deepcopy,假设列表里面有嵌套,为了安全比较建议用deepcopy:

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)] 

可以看到只有deepcopy的拷贝跟之前的列表再无瓜葛。



下面我们来深入看一下列表拷贝的内部原理,假设我们有这样两个列表:

list_1=['01','98'] 
list_2=[['01','98']] 

我们想得到两个列表的拷贝,从第一个列表开始,假设我们就用“=”来操作:

copy=list_1 

如果你认为这就完了,那么你就错了,来看看id函数返回的是什么:

print(id(copy)) 
print(id(list_1)) 

输出是:

4329485320 
4329485320 

两个列表是同一个id,意味着指向同一块内存。我们都知道python不存储任何东西在变量里,变量只是一个存储真实内容的一个内存块引用。这里对象是个list,但是我们创造了两个引用指向同一个内存地址,也就是这个列表有两个不同的名字:

图中list_1和copy指向的对象相同,所以当你想改变其中一个列表的元素的时候,另一个列表也会相应变动。

copy[0]="modify" 
print(copy) 
print(list_1) 

['modify', '98'] 
['modify', '98'] 

下面我们来换一种方式进行拷贝:

copy_1=list_1[:] 

这种方法解决了上述问题:

print(id(copy_1)) 
print(id(list_1)) 

4338792136 
4338791432 

图示如下:

现在我们来改变其中一个列表看看会有什么结果:

copy_1[0]="modify" 

print(list_1) 
print(copy_1) 

输出是:

['01', '98'] 
['modify', '98'] 

可见我们的变动只影响了copy_1并没有影响list_1。


事情到这里并没有结束,我们再来看看嵌套的列表

copy_2=list_2[:] 

copy_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 

print(id(deep[0])) 
print(id(list_2[0])) 
4322145992 
4322145800 

可以看到从内到外id都不同了,这里就完成了从外到内的拷贝。

当执行deepcopy时,内部动作下图所示:

我们最后再来检查下有没有解决之前的问题:

deep[0][1]="modify" 
print(list_2,deep) 
[['01', '98']] [['01', 'modify']] 

可以看到,改变的只有拷贝的列表,源列表并没有变。


这里还有各种深度拷贝的性能测试

METHOD                  TIME TAKEN 
b = [*a]                2.75180600000021 
b = a * 1               3.50215399999990 
b = a[:]                3.78278899999986  # Python2 winner (see above) 
b = a.copy()            4.20556500000020  # Python3 "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 

这里你可以自主选择最快的列表克隆方法了。

参考性能脚本如下:

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)) 

参考链接:https://stackoverflow.com/questions/2612802/how-to-clone-or-copy-a-list/2612815#2612815

11 views

©2020 by EasyCSTech. Special thanks to IPinfo​ and EasyNote.