np 数组的地址传递、view 与 copy
前言
numpy 是 python 众多第三方模块中非常实用高效的一个,但是在使用 numpy 的过程中,我也遇到了不少问题。其中这个 np 数组的赋值方式就十分令人困惑。
在使用 python 的 list 时,如果对一个 list 进行切片,那么返回的就是一个新的 list 。例如下面的代码:
>>> a = [1, 2, 3]
>>> b = a[:]
>>> b[1] = 999
>>> a
[1, 2, 3]
此时, b 是 a 的一个复制,对 b 进行修改时不会影响 a 中元素的值。
但是这个特性在 np 数组中就不成立了。
>>> a = np.array([1, 2, 3])
>>> b = a[:]
>>> b[1] = 999
>>> a
array([ 1, 999, 3])
因为这个原因,我之前一直无法找出某个程序的错误,后来才知道 在 np 数组中切片并不是对原数组进行 copy ,而是一个进行了 view 的赋值方法。
因此,我打算在这篇博客中深入区分一下 numpy 数组的三种赋值方式,防止再因为不同的赋值方式导致程序出错。
1. 地址的传递
赋值时,python 只会将原 numpy 数组的地址传递给另一个变量
>>> a = np.arange(12)
>>> a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> b = a
>>> b is a
True
此时, b 与 a 的地址相同,指向同一个 np 数组。
如果在这个时候改变 b 的大小,那么 a 的大小也会改变。
>>> b.shape = 3, 4
>>> b
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a.shape
(3, 4)
将 np 数组作为函数参数传入时,也会直接将地址传入。
>>> def f(x):
… print(id(x))
…
>>> id(a)
4466696320
>>> f(a)
4466696320
2. view
view 函数可以创建一个一一对应的新 np 数组。如果对 view 中的数据进行修改,原数组中对应位置的数据也会修改
>>> c = a.view()
>>> c is a
False
>>> c.base is a
True
其中 c.base 指向的就为原始数据。
此时,如果改变 c 的形状, a 的大小并不会发生改变。但是如果我们修改其中的某个位置的元素值时,在 a 中对应位置的元素也会变化
>>> c.shape = 2, 6
>>> a.shape
(3, 4)
>>> c[0, 4] = 1234
>>> a
array([[ 0, 1, 2, 3],
[1234, 5, 6, 7],
[ 8, 9, 10, 11]])
如果对一个 numpy 数组进行切片时,返回的是一个 view
>>> s = a[:, 1:3]
>>> s[:] = 10
>>> a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
3. copy
使用 copy 函数可以完全拷贝原数组中的所有值。
>>> d = a.copy()
>>> d is a
False
>>> d.base is a
False
>>> d[0,0] = 9999
>>> a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
结语
无论是指针传递、 view 还是 copy ,其实他们都具有各自的优缺点。在使用时我们也需要灵活地选择三种赋值方法,并且小心使用不恰当的方法所带来的意料之外的错误。