# ndarray是什么

NumPy provides an N-dimensional array type, the ndarray, which describes a collection of “items” of the same type. The items can be indexed using for example N integers.

ndarray是numpy中的多维数组，数组中的元素具有相同的类型，且可以被索引

>>> import numpy as np
>>> a = np.array([[0,1,2,3],[4,5,6,7],[8,9,10,11]])
>>> a
array([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
>>> type(a)
<class 'numpy.ndarray'>
>>> a.dtype
dtype('int32')
>>> a[1,2]
6
>>> a[:,1:3]
array([[ 1,  2],
[ 5,  6],
[ 9, 10]])

>>> a.ndim
2
>>> a.shape
(3, 4)
>>> a.strides
(16, 4)


# ndarray的设计哲学

ndarray的设计哲学在于数据存储与其解释方式的分离，或者说copyview的分离，让尽可能多的操作发生在解释方式上（view上），而尽量少地操作实际存储数据的内存区域。

>>> a
array([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
>>> b = a.reshape(4, 3)
>>> b
array([[ 0,  1,  2],
[ 3,  4,  5],
[ 6,  7,  8],
[ 9, 10, 11]])

# reshape操作产生的是view视图，只是对数据的解释方式发生变化，数据物理地址相同
>>> a.ctypes.data
80831392
>>> b.ctypes.data
80831392
>>> id(a) == id(b)
false

# 数据在内存中连续存储
>>> from ctypes import string_at
>>> string_at(b.ctypes.data, b.nbytes).hex()
'000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b000000'

# b的转置c，c仍共享相同的数据block，只改变了数据的解释方式，“以列优先的方式解释行优先的存储”
>>> c = b.T
>>> c
array([[ 0,  3,  6,  9],
[ 1,  4,  7, 10],
[ 2,  4,  8, 11]])
>>> c.ctypes.data
80831392
>>> string_at(c.ctypes.data, c.nbytes).hex()
'000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b000000'
>>> a
array([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])

# copy会复制一份新的数据，其物理地址位于不同的区域
>>> c = b.copy()
>>> c
array([[ 0,  1,  2],
[ 3,  4,  5],
[ 6,  7,  8],
[ 9, 10, 11]])
>>> c.ctypes.data
80831456
>>> string_at(c.ctypes.data, c.nbytes).hex()
'000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b000000'

# slice操作产生的也是view视图，仍指向原来数据block中的物理地址
>>> d = b[1:3, :]
>>> d
array([[3, 4, 5],
[6, 7, 8]])
>>> d.ctypes.data
80831404
>>> print('data buff address from {0} to {1}'.format(b.ctypes.data, b.ctypes.data + b.nbytes))
data buff address from 80831392 to 80831440



• 1、numpy 的切片操作返回原数据的视图。
• 2、调用 ndarray 的 view() 函数产生一个视图。

• Python 序列的切片操作，调用deepCopy()函数。
• 调用 ndarray 的 copy() 函数产生一个副本。

—— from NumPy 副本和视图

view机制的好处显而易见，省内存，同时速度快

# ndarray的内存布局

NumPy arrays consist of two major components, the raw array data (from now on, referred to as the data buffer), and the information about the raw array data. The data buffer is typically what people think of as arrays in C or Fortran, a contiguous (and fixed) block of memory containing fixed sized data items. NumPy also contains a significant set of data that describes how to interpret the data in the data buffer.

—— from NumPy internals

ndarray的内存布局示意图如下：

• raw array data：为一个连续的memory block，存储着原始数据，类似C或Fortran中的数组，连续存储

• dtype数据类型，指示了每个数据占用多少个字节，这几个字节怎么解释，比如int32float32等；
• ndim：有多少维；
• shape：每维上的数量；
• strides维间距，即到达当前维下一个相邻数据需要前进的字节数，因考虑内存对齐，不一定为每个数据占用字节数的整数倍；

>>> a.flags
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False


ndarray的底层是C和Fortran实现，上面的属性可以在其源码中找到对应，具体可见PyArrayObjectPyArray_Descr等结构体。

# 为什么可以这样设计

>>> a
array([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
>>> a[1,1]
5
>>> i,j = a[1,1], a[1,1]

# i和j为不同的对象，访问一次就“组装一个”对象
>>> id(i)
102575536
>>> id(j)
102575584
>>> a[1,1] = 4
>>> i
5
>>> j
5
>>> a
array([[ 0,  1,  2,  3],
[ 4,  4,  6,  7],
[ 8,  9, 10, 11]])

# isinstance(val, np.generic) will return True if val is an array scalar object. Alternatively, what kind of array scalar is present can be determined using other members of the data type hierarchy.
>> isinstance(i, np.generic)
True


# 小结

• ndarray的设计哲学在于数据与其解释方式的分离，让绝大部分多维数组操作只发生在解释方式上
• ndarray中的数据在物理内存上连续存储，在读取时根据dtype现组装成对象输出，可以按秩访问，效率高省空间
• 之所以能这样实现，在于ndarray是为矩阵运算服务的，所有数据单元都是同种类型

# 参考

posted @ 2020-02-10 22:14  shine-lee  阅读(3867)  评论(0编辑  收藏  举报