python列表复制问题
2019年03月06日

问题发现

在字典/列表等对象里套用字典/对象,并且复制的时候会出现这个情况:

a={
'a1':{'line':'l1','param':[0,1,2]},
'a2':{'line':'l2','param':2}
}
print(a)
b=a['a1'].copy()
b['param'][0]=10
print('===a=====')
print(a)
print('===b=====')
print(b)

执行结果是:

===a before=====
{'a1': {'line': 'l1', 'param': [0, 1, 2]}, 'a2': {'line': 'l2', 'param': 2}}
===a after=====
{'a1': {'line': 'l1', 'param': [10, 1, 2]}, 'a2': {'line': 'l2', 'param': 2}}
===b after=====
{'line': 'l1', 'param': [10, 1, 2]}

可以发现,为了复制字典a中的字典a1给b,虽然b复制过去了,但是修改b的数值时会将a的值也一并修改。

问题分析

原因和python的复制方式有关,python一共分三种复制

直接赋值

直接赋值就是简单的=,由于python中数字都有固定id,比如语句c = 1意思是将1的id地址放到c的内容中。

而对于列表来说,c = [1,2,3]的意思就是创建了一个新地址ADDR,里面存储[1,2,3],这个地址赋给了c的内容。所以当再加上一句d = c之后,对d的操作同时也就是对c操作,这时候ADDR也同时赋给了d,所以当修改ADDR里面的内容时,c、d同时变化。

浅复制

和下面的深复制一看就知道区别。浅复制一般是使用自带的函数copy(),如:

c = [1,2,3]
d = c.copy()

那么这个时候,d获得了[1,2,3]所存放的地址ADDR里的值,放到了ADDR2里,此时c和d没有任何关系,完全是两个陌路人。

既然叫做浅复制,那么肯定的,这个复制只覆盖了一层,也就是说,假如c中存储的ADDR,其内容不是[1,2,3],而是另一个列表e的地址,那只能复制到ADDR这一层:

e = [1,2,3]
c = [e,4,5]
d = c.copy()
print(c)
d[0][0]=10
d[1]=40
print(c)
print(d)

执行结果是:

[[1, 2, 3], 4, 5]
[[10, 2, 3], 4, 5]
[[10, 2, 3], 40, 5]

明显的,c中存储的第一个数据是e的地址,所以即便拷给了d,仍然是指向e,c和d共享e的变化,然而后面的4,5就互相没有关系。

浅复制有几种方式:

当然上面的方式都只复制第一层

深复制

深复制即完全复制所有内容,复制之后的对象和原来没有任何关系。使用时需要import模块copy,使用deepcopy()复制。

解决方案

有多层嵌套的时候,用deepcopy()是最方便的做法。对于特定需求下,也可以采用浅复制的办法,但是记住只复制一层,要继续叠加层次时就要考虑还用不用这个弱鸡的copy()。

#solution 1
from copy import deepcopy
a={
'a1':{'line':'l1','param':[0,1,2]},
'a2':{'line':'l2','param':2}
}
print('===a before=====')
print(a)
b=deepcopy(a['a1'])
b['param'][0]=10
print('===a after=====')
print(a)
print('===b after=====')
print(b)

#solution 2
from copy import deepcopy
a={
'a1':{'line':'l1','param':[0,1,2]},
'a2':{'line':'l2','param':2}
}
print('===a before=====')
print(a)
b=deepcopy(a['a1'])
b['param'][0]=10
print('===a after=====')
print(a)
print('===b after=====')
print(b)

运行结果如下,其实有时候复制a,需要的其实只是a的一小部分,全部复制有时候是挺浪费资源的。

#solution 1:
===a before=====
{'a1': {'line': 'l1', 'param': [0, 1, 2]}, 'a2': {'line': 'l2', 'param': 2}}
===a after=====
{'a1': {'line': 'l1', 'param': [0, 1, 2]}, 'a2': {'line': 'l2', 'param': 2}}
===b after=====
{'line': 'l1', 'param': [10, 1, 2]}

#solution 2:
===a before=====
{'a1': {'line': 'l1', 'param': [0, 1, 2]}, 'a2': {'line': 'l2', 'param': 2}}
===a after=====
{'a1': {'line': 'l1', 'param': [0, 1, 2]}, 'a2': {'line': 'l2', 'param': 2}}
===b after=====
[10, 1, 2]

Problem solved.

--