揭开Python科学计算的面纱

春牛春杖。无限春风来海上。便与春工。染得桃红似肉红。
春幡春胜。一阵春风吹酒醒。不似天涯。卷起杨花似雪花。

  标准的Python中用列表保存一组值,可以当做数组使用,但是由于其值类型任意,所以列表中保存的是指针,这样的话保存一个简单的列表,例如:[1,2,3]需要三个指针和三个对象。对于数值运算来说这样十分耗费内存和CPU。

  Python提供了array 模块,他所提供的array对象和列表不同,能直接保存数值和C语言的一维数组相似,但是他不支持多维数组,也没有各种运算函数,因此也不适合做数值运算。

  NumPy的诞生弥补了这些不足之处,NumPy提供了两种基本的对象:

  1. ndarray:英文全称n-dimensional array object ,他是储存单一数据类型的多维数组,后来统称为数组。
  2. ufunc:英文全称为universal function object , 他是一种能够对数组进行特殊处理的函数。

本文采用1.12版本

>>> import numpy as np
>>> np.__version__
'1.12.0'

ndarray对象


  Numpy中使用ndarray对象来表示数组,他是整个库的核心对象,NumPy中所有的函数都是围绕这ndarray对象展开处理的。ndarray的结构不复杂,但是功能十分强大,不但可以用它大量高效的储存数值元素,从而提高数组计算的运算速度,还可以用它和各种扩展库进行数据交换。

创建

  首先需要创建数组才能对其进行运算和操作。可以通过给array() , 函数传递Python的序列对象创建数组,如果传递的是多层嵌套的序列,将创建个多维数组:

>>> a = np.array([1,2,3,4])
>>> b = np.array([5,6,7,8])
>>> c = np.array([[1,2,3,4],[4,5,6,7],[7,8,9,0]])
>>> a
array([1, 2, 3, 4])
>>> b
array([5, 6, 7, 8])
>>> c
array([[1, 2, 3, 4],
       [4, 5, 6, 7],
       [7, 8, 9, 0]])
>>> a.shape                    # 查看数组的形状可以通过其 shape属性
(4,)
>>> b.shape
(4,)
>>> c.shape
(3, 4)

  数组a的shape属性只有一个元素是因为他是一维数组 . 而数组C的shape属性有两个元素, 是因为他是二维数组 . 其元素的排序是从高维到低维 . 我们还可以通过修改数组的shape属性,在保持数组元素个数不变的情况下,改变数组每个轴的长度.下面的例子讲数组c的shape属性改为(4,3), 注意从(3,4)到(4,3)不是对数组进行转置, 而是改变每个轴的大小,数组元素在内存中的位置没有变化

>>> c.shape = 4,3
>>> c
array([[1, 2, 3],
       [4, 4, 5],
       [6, 7, 7],
       [8, 9, 0]])

  你应该可以发现(4,3)和(3,4)总结起来不还是12个基本元素么 ? 我们改变总容量行不行 ?

>>> c.shape = 4,2
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    c.shape = 4,2
ValueError: cannot reshape array of size 12 into shape (4,2)
>>> c.shape = 4,5
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    c.shape = 4,5
ValueError: cannot reshape array of size 12 into shape (4,5)

  当我们设置某个轴的元素个数为 - 1 的时候, 讲自动计算此轴的长度 . 由于数组c有12个元素因此下面的程序讲c的shape属性改为(2,6):

>>> c.shape = 2,-1
>>> c
array([[1, 2, 3, 4, 4, 5],
       [6, 7, 7, 8, 9, 0]])
>>> c.shape 
(2, 6)

  如果是三维数组我们设置两个 -1 呢 ?( 这个问题好像有点弱智 哈 )  发生错误:只能指定一个位置的维度

>>> c = np.array([[[1,2,3,4],[4,5,6,7],[7,8,9,0]],[[1,2,3,4],[4,5,6,7],[7,8,9,0]
]])
>>> c.shape
(2, 3, 4)
>>> c.shape = 3,-1,-1
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    c.shape = 3,-1,-1
ValueError: can only specify(指定) one unknown dimension(维度)

  使用数组的reshape方法, 可以创建指定形状的新数组, 而原数组的形状保持不变:

>>> a
array([1, 2, 3, 4])
>>> d = a.reshape((2,2))
>>> d
array([[1, 2],
       [3, 4]])

  汇编/C语言 中毒比较深的同学可能问了, 那么储存方式呢 ? 其实数组a和d是共享储存空间的,因此如果修改其中任意一个数组的元素都会同事修改另一个数组的内容, 注意在下面的例子中 , 数组d中的2也被改变成了100:(很好,很强大.)

>>> a
array([1, 2, 3, 4])
>>> d = a.reshape((2,2))
>>> d
array([[1, 2],
       [3, 4]])
>>> a[1]
2
>>> a[1] = 100
>>> a
array([  1, 100,   3,   4])
>>> d
array([[  1, 100],
       [  3,   4]])

元素类型

  数组的元素类型可以通过dtype来获得, 在前面的例子中, 创建数组所用到的序列的元素都是证书, 因此创建的数组的元素类型也都是整数, 我用的是Ubuntu64位, 所以我显示的类型是int类型64位, 如果你的是32 位可能你用的是32位的系统.

>>> c
array([[1, 2, 3, 4, 4, 5],
       [6, 7, 7, 8, 9, 0]])
>>> c.dtype
dtype('int64')

  如果有编译器有代码提示的同学可能就会发现, 那个dtype了, 在没有介绍前面的东西的时候你可能还以为这是个什么鬼 ? 现在应该知道他是用于指定元素类型用的了吧?

>>> ai32 = np.array(
┌──────────────────────────────────────────────────────────────┐
│ np.array: (object, dtype=None, copy=True, order='K',         │
│ subok=False, ndmin=0)                                        │
│ array           array2string    array_equal                  │
│ array_equiv     array_repr      array_split                  │
│ array_str                                                    │
│ array(object, dtype=None, copy=True, order='K', subok=False, │
│  ndmin=0)                                                    │
│                                                              │
│ Create an array.                                             │
│                                                              │
│ Parameters                                                   │
│ ----------                                                   │
│ object : array_like                                          │
│     An array, any object exposing the array interface, an ob │
│ ject whose                                                   │
│     __array__ method returns an array, or any (nested) seque │
│ nce.                                                         │
│ dtype : data-type, optional                                  │
└──────────────────────────────────────────────────────────────┘
>>> ai32 = np.array([1,2,3,4],dtype = np.int32)
>>> ai32
array([1, 2, 3, 4], dtype=int32)
>>> ai32.shape
(4,)
>>> af = np.array([1,2,3,4],dtype = np.float)
>>> af
array([ 1.,  2.,  3.,  4.])
>>> ac = np.array([1,2,3,4],dtype = np.complex)
>>> ac
array([ 1.+0.j,  2.+0.j,  3.+0.j,  4.+0.j])
>>> ac.shape
(4,)
>>> ai32.dtype
dtype('int32')
>>> af.dtype
dtype('float64')
>>> ac.dtype
dtype('complex128')

  在上面的例子中传递给dtype参数的都是类型对象, 其中类型对象都是NumPy定义的数据类型. 当然内置的也有并且其效果和内置的相同.

  在需要指定dtype参数的时候, 也可以传递一个字符串来表示元素的数值类型. NumPy中每个数值类型都有集中字符串的表达方式, 字符串和类型之间的对用关系都储存在typeDict字典中,下面的程序获得与float64类型对应的所有键值.

>>> [key for key, value in np.typeDict.items() if value is np.float64]
[12, 'd', 'float_', 'float', 'double', 'Float64', 'f8', 'float64']

  好吧翻译一下.

>>> for key,value in np.typeDict.items():
...     if value is np.float64:
...         value,key
...         
...     
... 
(<class 'numpy.float64'>, 12)
(<class 'numpy.float64'>, 'd')
(<class 'numpy.float64'>, 'float_')
(<class 'numpy.float64'>, 'float')
(<class 'numpy.float64'>, 'double')
(<class 'numpy.float64'>, 'Float64')
(<class 'numpy.float64'>, 'f8')
(<class 'numpy.float64'>, 'float64')

  完整的类型列表可以通过下面的语句获得, 他讲typeDict字典中的所有值转化为一个集合,从而去除重复项. ( 重复项 值只有这么多, 但是一个值可以有很多个键. )

>>> set(np.typeDict.values())
{<class 'numpy.float128'>, <class 'numpy.uint64'>, <class 'numpy.int64'>, <class
 'numpy.str_'>, <class 'numpy.complex128'>, <class 'numpy.float64'>, <class 'num
py.uint32'>, <class 'numpy.int32'>, <class 'numpy.bytes_'>, <class 'numpy.comple
x64'>, <class 'numpy.float32'>, <class 'numpy.uint16'>, <class 'numpy.int16'>, <
class 'numpy.bool_'>, <class 'numpy.timedelta64'>, <class 'numpy.float16'>, <cla
ss 'numpy.uint8'>, <class 'numpy.int8'>, <class 'numpy.object_'>, <class 'numpy.
datetime64'>, <class 'numpy.uint64'>, <class 'numpy.int64'>, <class 'numpy.void'
>, <class 'numpy.complex256'>}

  上面显示的数值类型与数组的dtype属性是不同的对象. 通过dtype 对象的type属性可以获得与之对应的数值类型.

>>> c.dtype   # 我的理解是这里的int64 指的是数组中的元素是 int64储存的
dtype('int64')
>>> c.dtype.type  # 这里应该是数组本身是按照 numpy.int64的方式储存的 , 不知道对不对, 如果不对我会回来修改的.
<class 'numpy.int64'>

  通过NumPy的数值类型也可以创建数值对象, 下面创建一个16位的符号整数对象, 它与Python的整数对象不同的是,他的取值范围有限, 因此计算200*200 会溢出,得到一个负数.

>>> a = np.int16(200)
>>> a*a
__console__:1: RuntimeWarning: overflow encountered in short_scalars
-25536
>>> a+a
400

  另外值得指出的是, NumPy的数值对象的运算速度比Python的内置类型的运算速度慢的很多, 如果程序中需要大量的对单个数值进行计算, 应当避免使用NumPy的数值对象. 使用astype()方法可以对数组的元素类型进行转换.

>>> t1 = np.array([1,2,3,4.0],dtype = np.float64)
>>> t2 = np.array([1,2,3,4],dtype = np.complex)
>>> t3 = t1.astype(np.int64)
>>> t4 = t2.astype(np.complex256)
>>> t1.dtype
dtype('float64')
>>> t2.dtype
dtype('complex128')
>>> t3.dtype
dtype('int64')
>>> t4.dtype
dtype('complex256')
>>> t4.dtype.type
<class 'numpy.complex256'>

 自动生成数组

  前面的例子都是先创建一个Python的序列对象,然后通过array() 将其装华为数组, 这样做显然效率不高 , 因此NumPy 提供了很多专门用于创建数组的函数 , 下面的每个函数都有一些关键字参数. 具体用法请查看函数说明.

  arange() 类似于内置函数range() , 通过指定开始值, 终止值, 和步长来创建表示等差数列的一维数组, 注意所得到的结果不包含终止值.

>>> np.arange(0,1,0.1)
array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9])

  linspace 通过指定开始值和终止值以及步数,来创建一个数组. (line space : 线性等分 ?)   这个默认包含终止值.( 不然没法弄了)

>>> np.linspace(0,8,9)
array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.])

  当然我们可以通过指定endpoint的真假 来选择是否包含终止值.

>>> np.linspace(0,8,9)
array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.])
>>> np.linspace(0,8,9,endpoint = False)
array([ 0.        ,  0.88888889,  1.77777778,  2.66666667,  3.55555556,
        4.44444444,  5.33333333,  6.22222222,  7.11111111])
>>> 0.88888888*9
7.99999992
>>> np.linspace(0,7,9)
array([ 0.   ,  0.875,  1.75 ,  2.625,  3.5  ,  4.375,  5.25 ,  6.125,  7.   ])

  logspace()和linspace()类似, 不过他所创建的数组是等比数列.下面例子产生从100 到 10,有5个元素的等比数列, 注意起始值0 代表100 终止值 2 代表 102 

>>> np.logspace(0,2,num = 4,endpoint = False,base = 4.0,dtype = np.float128)
array([ 1.0,  2.0,  4.0,  8.0], dtype=float128)

  我用的bpython 在写代码的时候会有函数说明

 np.logspace: (start, stop, num=50, endpoint=True, base=10.0, │
│ dtype=None)                                                  │
│ logspace                                                     │
│ Return numbers spaced evenly on a log scale.                 │
│                                                              │
│ In linear space, the sequence starts at ``base ** start``    │
│ (`base` to the power of `start`) and ends with ``base ** sto │
│ p``                                                          │
│ (see `endpoint` below).                                      │
│                                                              │
│ Parameters                                                   │
│ ----------                                                   │
│ start : float                                                │
│     ``base ** start`` is the starting value of the sequence. │
│ stop : float                                                 │
│     ``base ** stop`` is the final value of the sequence, unl │
│ ess `endpoint`                                               │
│     is False.  In that case, ``num + 1`` values are spaced o │
└──────────────────────────────────────────────────────────────┘

   zeros(), ones(), empty()可以创建指定形状的和类型的数组. 其中empty() 只分配数组所使用的内存, 不对数组进行初始化操作, 所以他的运行速度应该是最快的. 下面的程序位创建一个形状为(2,3), 元素类型位整数的数组, 注意其中的元素没有被初始化.

  

>>> np.empty((2,3),np.int)
array([[              0,        10914432,        10743840],
       [140263681062408, 140263663167672, 140263663165992]])

   而zeros() 将数组初始化位0, ones()将数组元素初始化为1 . 下面创建一个长度位4, 而元素类型为整数的一维数组, 并且元素全部被初始化位0 :

>>> np.zeros(4,np.int)
array([0, 0, 0, 0])

  full() 可以讲元素初始化为指定的值:

>>> np.full((2,3),4,np.int64)
array([[4, 4, 4],
       [4, 4, 4]])

  编译器有代码提示的同学可能很早就注意到了, zeros_like(), ones_like(), empty_like(), full_like() 等函数了. 这些函数创建与参数数组的形状和类型相同的数组, 因此zeros_like(a) 和zeros(a.shape,a.dtype)的效果相同

>>> np.full(4,np.pi)
array([ 3.14159265,  3.14159265,  3.14159265,  3.14159265])
>>> np.full((4,2),np.pi)
array([[ 3.14159265,  3.14159265],
       [ 3.14159265,  3.14159265],
       [ 3.14159265,  3.14159265],
       [ 3.14159265,  3.14159265]])

  frombuffer(), fromstring(), fromfile() 等函数可以从字节序列, 或者文件创建数组. 下面以fromstring()为例子 , 展示他们的用法,  很明显这里值为-1 的一般都是自动计算.

>>> s = 'abcdefgh'
>>> #python的字符串实际上是一个字节序列每个字符,占有一个字节. 因此如果从s中创建L编码
>>> np.fromstring(s,np.int8,5)
array([ 97,  98,  99, 100, 101], dtype=int8)
>>> np.fromstring(s,np.int8,-1)
array([ 97,  98,  99, 100, 101, 102, 103, 104], dtype=int8)

存取元素

  可以使用和列表相同的方式对数组的元素进行存取:

>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  1. a[5]: 用整数作为下标可以获取数组中的某个元素.
  2. a[3:5]: 用切片作为下标获取数组的一部分, 不包括a[5].
  3. a[:5]: 切片中省略开始下标, 表示从 a[0] 开始.
  4. a[:-1]: 下标使用负数, 表示从数组最后往前数.

  灵活灵活滴 .

>>> a[5]
5
>>> a[5:]
array([5, 6, 7, 8, 9])
>>> a[5:6]
array([5])
>>> a[5:8]
array([5, 6, 7])
>>> a[5:-1]
array([5, 6, 7, 8])
>>> a[-3:-1]
array([7, 8])
>>> a[3:-1]
array([3, 4, 5, 6, 7, 8])

  当使用证书列表对数组元素进行存取的时候,将使用列表中的每个元素作为下表, 使用列表作为下标得到的数组不和原始数组共享数据.

>>> x = np.arange(10,1,-1)
>>> x
array([10,  9,  8,  7,  6,  5,  4,  3,  2])
>>> a = x[[3,3,1,8]]
>>> a
array([7, 7, 9, 2])
>>> b = x[[3,3,-3,8]]
>>> b
array([7, 7, 4, 2])

  * 我们也可以通过上面的方式来进行批量赋值 .

>>> x[[0,1,2,3]] = 9,8,7,6
>>> x[[0,1,2,3]]
array([9, 8, 7, 6])
>>> x
array([9, 8, 7, 6, 6, 5, 4, 3, 2])

   * 我们可以通过数组为下标的情况来获得一个新的数组, 在一位数组中是这样, 在多维中也是这样.

array([10,  9,  8,  7,  6,  5,  4,  3,  2])
>>> x[np.array([[3,3,1,8],[3,3,-3,8]])]
array([[7, 7, 9, 2],
       [7, 7, 4, 2]])

  * 当使用不二数组b 作为存取数组 x 中的元素时, 将获得数组x 中与 数组b 中True对应的元素. 使用布尔数组作为下表获得的数组不和原始数组共享数据内存,  注意这种方式只对应与不二数组, 而不能使用布尔列表.

>>> x[np.array([True,False,True])]
__console__:3: VisibleDeprecationWarning: boolean index did not match indexed ar
ray along dimension 0; dimension is 9 but corresponding boolean dimension is 3
array([10,  8])

  * 当布尔数组的长度不够的时候, 不够的部分都当做False

  *布尔数组一般不是手工产生的, 而是通过使用布尔运算的ufunc 函数产生的, 关于ufunc 函数请参照下一节课的介绍, 下面我们举一个简单的例子说明布尔数组下标的用法:

>>> np.random.randint(0,10,6)
array([1, 9, 5, 0, 7, 2])
>>> x[x>5]
array([10,  9,  8,  7,  6])
>>> x
array([10,  9,  8,  7,  6,  5,  4,  3,  2])

  多维数组

  多维数组的存取和一维数组类似, 因为多维数组有多个轴, 所以他的下标需要用多少个值来表示, NumPy采用元祖作为数组的下标, 元祖中的每个元素和数组的每个轴对应.

  为什么使用元祖作为下标.

  Python的下标语法(用[] 存取序列中的元素)本身并不支持多维, 但是可以使用任何对象作为下标, 因此 NumPy使用元祖作为下标存取数组中的元素, 使用元祖可以很方便的表示多个轴的下标, 虽然在Python程序中经常使用圆括号将元祖中的元素括起来, 但其实元祖的使用语法,只需要逗号隔开元素即可, 例如 x,y = y,x 就是用元祖交换变量值的一个例子. 因此 a[1,2]和啊a[(1,2)] 完全相同, 都是使用元祖(1,2) 作为数组a 的下标.

  `上面的二维数组是怎样创建的? 其实他是一个加法表, 由纵向量(0,10,20,30,40,50)和横向量(0,1,2,3,4,5)的元素想家而得的, 可以使用下面的语句创建他, 至于其原理, 将在后面的章节讨论.

>>> a = np.arange(0,60,10).reshape(-1,1) + np.arange(0,5)
>>> a
array([[ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14],
       [20, 21, 22, 23, 24],
       [30, 31, 32, 33, 34],
       [40, 41, 42, 43, 44],
       [50, 51, 52, 53, 54]])

  有的同学应该很好奇上面的图中的是怎样让a[0,3:5] , a[4:,4:]等切片操作找到自己的位置的 ?  其中的第0 个元素个 第 0 轴对应, 第一个元素和第一轴对应 , 然后就是普通的一维数组切片操作了, 在不同的维度中可以对应出来他们相交的点 , 相交的点就是 该切片操作表示的点.

  

  如果下标元祖中只包含整数和切片, 那么得到的数组和原始数组共享数据 , 他是原数组的视图 , 下面的例子中, 数组b是a的视图 , 他们共享数据 , 因此修改b[0]的时候, 数组a中对应的元素也会被修改.

>>> b = a[0,3:5]
>>> b
array([3, 4])
>>> b[0] = 12312313
>>> b
array([12312313,        4])
>>> a
array([[       0,        1,        2, 12312313,        4],
       [      10,       11,       12,       13,       14],
       [      20,       21,       22,       23,       24],
       [      30,       31,       32,       33,       34],
       [      40,       41,       42,       43,       44],
       [      50,       51,       52,       53,       54]])

  切片对象(slice)

  根据Python的语法,在[]中可以使用冒号隔开的两个或者三个整数表示切片, 但是单独生成切片对象时需要使用slice() 来创建. 他有三个参数值, 开始,结束,间隔. 当这些值需要省略时可以使用None, 例如

>>> a[:,2]
array([ 2, 12, 22, 32, 42, 52])
>>> a[slice(None,None,None),2]
array([ 2, 12, 22, 32, 42, 52])
>>> a[slice(2,3),]
array([[20, 21, 22, 23, 24]])
>>> b = a[slice(2,3),] 
>>> b
array([[20, 21, 22, 23, 24]])
>>> a
array([[ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14],
       [20, 21, 22, 23, 24],
       [30, 31, 32, 33, 34],
       [40, 41, 42, 43, 44],
       [50, 51, 52, 53, 54]])
>>> b[0]
array([20, 21, 22, 23, 24])
>>> b[0][0] = 123123
>>> b
array([[123123,     21,     22,     23,     24]])
>>> a
array([[     0,      1,      2,      3,      4],
       [    10,     11,     12,     13,     14],
       [123123,     21,     22,     23,     24],
       [    30,     31,     32,     33,     34],
       [    40,     41,     42,     43,     44],
       [    50,     51,     52,     53,     54]])

  用Python的内置函数slice()创建下标实际上是比较麻烦的, 因此NumPy提供了一个s_对象来帮助我们创建数组下标, 请注意s_实际上是IndexExpression类的一个对象.(因为动不动就要填写None)

>>> b = a[slice(2,,1),] 
  File "<input>", line 1
    b = a[slice(2,,1),]
                  ^
SyntaxError: invalid syntax

  所以我们可以使用NumPy所提供的s_对象来帮助我们创建数组下标.

>>> a = np.arange(0,60,10).reshape(-1,1) + np.arange(0,5)
>>> a
array([[ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14],
       [20, 21, 22, 23, 24],
       [30, 31, 32, 33, 34],
       [40, 41, 42, 43, 44],
       [50, 51, 52, 53, 54]])
>>> b = a[slice(None,None,2),slice(2,None,None)]
>>> b
array([[ 2,  3,  4],
       [22, 23, 24],
       [42, 43, 44]])
>>> c = a[np.s_[::2,2:]]
>>> c
array([[ 2,  3,  4],
       [22, 23, 24],
       [42, 43, 44]])

  s_为什么不是函数?

    根据Python的语法, 只有在中括号[] 中才能使用以冒号隔开的切片语法, 如果s_ 是函数的话, 那么这些切片必须使用slice() 创建, 类似的对象还有mgrid 和 ogrid等, Python的下标语法实际上会调用getitem() 方法, 因此我们可以很容易实现s_对象的功能.

  在a[(0,1,2,3),(1,2,3,4)] 中, 下标仍然是一个有两个元素的元祖, 元祖中的每个元素都是一个整数元祖, 分别对应数组中的第0 轴和第 1 轴, 从两个序列的对应位置取出两个整数组成下标, 于是得到的结果是, a[0,1],a[1,2], a[2,3], a[3,4].

  在a[3:,[0,2,5]]中, 第0轴的下标是一个切片对象, 它选取第三行之后的所有行, 第一轴的下标是整数列表, 它选取 第0,2,5列.

  在a[mask,2]中, 第0轴的下标是一个布尔数组,他选取第0,2,5行,第一轴的下标是2.

>>> x = np.array([[0,1],[2,3]])
>>> y = np.array([[-1,-2],[-3,-4]])
>>> a[x,y]
array([[ 5, 14],
       [23, 32]])

结构数组

  在C语言中我们可以通过struct 关键字定义结构类型, 结构中的字段占据连续的内存空间. 类型相同的两个结构所占用的内存大小相同, 因此可以很容易的定义结构数组, 和C语言一样, 在NumPy中也很容易堆这种结构数组进行操作, 只要NumPy中的结构定义和C语言中的结构定义相同, 就可以很方便的读取C语言结构数组的二进制数据, 将其转化为NumPy的结构数组.

  假设 我们需要定义一个结构数组, 他的每个元素都有name , age 和 weight字段. 在NumPy中可以如下定义:

>>> persontype = np.dtype({
... 'names':['name','age','weight'],
... 'formats':['S30','i','f']},align = True)
>>> a = np.array([('zhang',32,75.5),('Wang',24,65.2)],dtype = persontype)
>>> a[1]
(b'Wang', 24,  65.19999695)

  我们先创建一个 persontype 的dtype对象, 他的参数是一个描述结构类型的各个字段的字典. 字典有两个键, 'name'和'format'. 每个键对应的值都是一个列表. 'names' 定义结构中的每个字段的名称, 而format 则定义为每个字段的类型, 我们这里使用类型字符串定义字符类型.

  •   'S30' 长度位30个字节的字符串类型. 由于结构中的每个元素的大小必须固定, 因此需要指定字符串的长度.
  •        ' i '  32位的整数类型相当于np.int32 . 
  •        ' f ' 32位的单精度浮点数.类型 相当于 np.float32

  然后调用array() 以创建数组, 通过dtype 参数指定所创建的数组的元素类型为 persontype. 下面查看 a的数组类型

>>> a.dtype
dtype({'names':['name','age','weight'], 'formats':['S30','<i4','<f4'], 'offsets'
:[0,32,36], 'itemsize':40}, align=True)

  还可以用包含多个元祖的列表来描述结构的类型:

dtype([('name','|S30'),('age','<i4'),('weight','<f4')])

  其中形如 "字段名,类型描述"的元祖描述了结构中的每个字段, 类型字符串前面的'|','<',">"等字符表示字段值的字节顺序:

  • | : 忽视字节顺序
  • <: 低位字节在前, 即小端模式.
  • >: 高位字节在前, 即大端模式.
  • 结构数组的存储方式和一般的数组相同, 通过下标能过取得其中的元素, 注意元素的值看上去像是数组, 实际上是结构.
>>> a[0]
(b'zhang', 32,  75.5)
>>> a[0].dtype
dtype({'names':['name','age','weight'], 'formats':['S30','<i4','<f4'], 'offsets'
:[0,32,36], 'itemsize':40}, align=True)
>>> a[0]['name']
b'zhang'
>>> a[0]['age']
32

   a[0] 是一个结构元素, 他和数组a 共享内存数据, 因此可以通过修改它的字段来改变原始数组中对应元素的字段.

>>> c = a[1]
>>> c
(b'Wang', 24,  65.19999695)
>>> c['age'] = 12312312312
>>> a[1]
(b'Wang', -572589576,  65.19999695)  # 溢出了
>>> b = a['age']
>>> b
array([        32, -572589576], dtype=int32)
>>> b[0] = 40
>>> b
array([        40, -572589576], dtype=int32)
>>> a[0]['age']
40

   我们可以通过a.tostring()或者a.tofile的方法,可以将数组a位2进制的方式转换成字符串或写入文件。

原文地址:https://www.cnblogs.com/A-FM/p/6517500.html