python中的深拷贝和浅拷贝问题
平时写Python用惯了numpy的矩阵类型,只用python自带的list做有关矩阵的(二维数组的)处理的时候碰到各种bug。这里是今日份的bug和解决方案。
问题缘起
在一个程序中,我们希望用list实现一个二维数组,然后对其中的元素挨个根据下标的指引来进行赋值。我们对这个二维数组也就是矩阵的初始化是这样的:
m, n = 5, 3
matrix = [[1] * n] * m
其中m,n分别是行数和列数。乍一看没有什么问题,但是在赋值的时候出现了这样的一幕:
matrix
Out[199]: [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]
matrix[1][0] = 233
matrix
Out[201]: [[233, 1, 1], [233, 1, 1], [233, 1, 1], [233, 1, 1], [233, 1, 1]]
???
我们发现,虽然用的是二维数组的下标,结果不仅仅我们的第1行第0列被赋值,而且所有其他行的第0列也跟着赋值了。这是什么原因呢?
问题就处在我们的初始化的方法上。
深拷贝和浅拷贝
在python 中,对一个list后面用 乘号 再加上 数字 的方法来初始化一个list,实际上是对这个list进行了浅拷贝(shallow copy),在python中,有深拷贝(deep copy) 和 浅拷贝 的区别。简单来讲就是:深拷贝就是把要拷贝的对象整体复制一份,存在新开辟的空间里;而浅拷贝指的是,对于要拷贝的对象要复制一份,但是对于其内部的子对象就不复制了,而是直接引用,也就是类似于添加了一个链接而已,如下:
import copy
a
Out[210]: [888, 2, 3, [4, 5]]
b = copy.copy(a)
b
Out[212]: [888, 2, 3, [4, 5]]
b[0] = 233
b
Out[214]: [233, 2, 3, [4, 5]]
a
Out[215]: [888, 2, 3, [4, 5]]
b[3][1] = 666
b
Out[217]: [233, 2, 3, [4, 666]]
a
Out[218]: [888, 2, 3, [4, 666]]
这里就很明显了,我们对a做一个浅拷贝,目的是b,然后我们对b进行操作,如果对list中的整数赋值,也就是对象中的元素赋值,那么就只改变b中的这个位置的元素值,而a的不变;但是如果我们对list中的list,也就是子对象进行赋值,那么我们法线,这个操作同样也影响了a中的结果。
copy 这个模块里的copy()是浅拷贝的函数,我们在试一下深拷贝deepcopy()函数:
a
Out[219]: [888, 2, 3, [4, 666]]
b = copy.deepcopy(a)
b
Out[221]: [888, 2, 3, [4, 666]]
b[0] = 1
b
Out[223]: [1, 2, 3, [4, 666]]
a
Out[224]: [888, 2, 3, [4, 666]]
b[3][0] = 233
b
Out[226]: [1, 2, 3, [233, 666]]
a
Out[227]: [888, 2, 3, [4, 666]]
这就很科学了!两个对象 a 和 b 互不打扰,很和谐。
另外,我们通常的赋值操作(list的直接赋值)又是怎样的结果呢?
a
Out[229]: [888, 2, 3, [4, 666]]
b = a
b
Out[231]: [888, 2, 3, [4, 666]]
b[0] = 1
b
Out[233]: [1, 2, 3, [4, 666]]
a
Out[234]: [1, 2, 3, [4, 666]]
b[3][0] = 1
b
Out[236]: [1, 2, 3, [1, 666]]
a
Out[237]: [1, 2, 3, [1, 666]]
可以看出来,直接赋值的结果是比浅拷贝还浅拷贝的。。。因为a和b根本上就是指向的同一个对象!也就是说,a和b是这个对象的两个引用(reference),修改一个就会改变另一个。
对上文中问题的解释
了解了这两个概念的区别,就可以解释上述的问题。首先,我们对
[1] * 3 # 创建了 1 的三个浅拷贝,得到了[1,1,1],此时我们修改某个1,不会影响其他的,因为这是int的元素,不是子对象
[[1] * 3] * 4 创建了list列表[1,1,1]的四个浅拷贝,这里得到的list是以list为元素类型的,因此改变一个就会影响其他
# 这就是我们开始会得到所有行都改变的原因,因为他们实际上指向的是同一个东西!
解决方法
改用如下方式初始化数组,就可以得到一个可以通过二维下标访问的矩阵了。
matrix = [[ 1 for i in range(n)] for i in range(m)]
这就是之前整理过的list comprehension的方法生成list。
(貌似这篇文章里有好多感叹号。。。因为学艺不精。。。被这个bug折腾了半天才发现它。。。好气。。。)
2018年03月30日20:07:40