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
,其实他们都具有各自的优缺点。在使用时我们也需要灵活地选择三种赋值方法,并且小心使用不恰当的方法所带来的意料之外的错误。