2.1.3 自动生成数组

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

arange()类似于内置函数range(),通过指定开始值、终值和步长来创建表示等差数列的一维数组,注意所得到的结果中不包含终值。例如下面的程序创建开始值为0、终值为1、步长为0.1的等差数组,注意终值1不在数组中:

    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()通过指定开始值、终值和元素个数来创建表示等差数列的一维数组,可以通过endpoint参数指定是否包含终值,默认值为True,即包含终值。下面两个例子分别演示了endpoint为True和False时的结果,注意endpoint的值会改变数组的等差步长:

   np.linspace(0, 1, 10, endpoint=False) # 步长为1/10
    array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9])

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

基数可以通过base参数指定,其默认值为10。下面通过将base参数设置为2,并设置endpoint参数为False,创建一个比例为21/12的等比数组,此等比数组的比值是音乐中相差半音的两个音阶之间的频率比值,因此可以用它计算一个八度中所有半音的频率:

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

    np.empty((2,3), np.int)
    array([[1078523331, 1065353216, 1073741824],
           [1077936128, 1082130432, 1084227584]])

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

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

full()将数组元素初始化为指定的值:

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

此外,zeros_like()、ones_like()、empty_like()、full_like()等函数创建与参数数组的形状和类型相同的数组,因此zeros_like(a)和zeros(a.shape, a.dtype)的效果相同。

frombuffer()、fromstring()、fromfile()等函数可以从字节序列或文件创建数组。下面以fromstring()为例介绍它们的用法,先创建含8个字符的字符串s:

    s = "abcdefgh"

Python的字符串实际上是一个字节序列,每个字符占一个字节。因此如果从字符串s创建一个8位的整数数组,所得到的数组正好就是字符串中每个字符的ASCII编码:

    np.fromstring(s, dtype=np.int8)
    array([ 97,  98,  99, 100, 101, 102, 103, 104], dtype=int8)

如果从字符串s创建16位的整数数组,那么两个相邻的字节就表示一个整数,把字节98和字节97当作一个16位的整数,它的值就是98*256+97=25185。可以看出,16位的整数是以低位字节在前(little-endian)的方式保存在内存中的。

    print 98*256+97
    np.fromstring(s, dtype=np.int16)
    25185
    array([25185, 25699, 26213, 26727], dtype=int16)

如果把整个字符串转换为一个64位的双精度浮点数数组,那么它的值是:

    np.fromstring(s, dtype=np.float)
    array([  8.54088322e+194])

显然这个结果没有什么意义,但是如果我们用C语言的二进制方式写了一组double类型的数值到某个文件中,那就可以从此文件读取相应的数据,并通过fromstring()将其转换为float64类型的数组,或者直接使用fromfile()从二进制文件读取数据。

fromstring()会对字符串的字节序列进行复制,而使用frombuffer()创建的数组与原始字符串共享内存。由于字符串是只读的,因此无法修改所创建的数组的内容:

    buf = np.frombuffer(s, dtype=np.int16)
    buf[1] = 10
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-52-f523db231ae5> in <module>()
          1 buf = np.frombuffer(s, dtype=np.int16)
    ----> 2 buf[1] = 10
    
    ValueError: assignment destination is read-only

Python中还有一些类型也支持buffer接口,例如bytearray、array.array等。在后面的章节中,我们会介绍如何使用这些对象实现动态数组的功能。

还可以先定义一个从下标计算数值的函数,然后用fromfunction()通过此函数创建数组:

    def func(i):
        return i % 4 + 1
    
    np.fromfunction(func, (10,))
    array([ 1.,  2.,  3.,  4.,  1.,  2.,  3.,  4.,  1.,  2.])

fromfunction()的第一个参数是计算每个数组元素的函数,第二个参数指定数组的形状。因为它支持多维数组,所以第二个参数必须是一个序列。上例中第二个参数是长度为1的元组(10,),因此创建了一个有10个元素的一维数组。

下面的例子创建一个表示九九乘法表的二维数组,输出的数组a中的每个元素a[i, j]都等于func2(i, j):

    def func2(i, j):
        return (i + 1) * (j + 1)
    np.fromfunction(func2, (9,9))
    array([[  1.,   2.,   3.,   4.,   5.,   6.,   7.,   8.,   9.],
           [  2.,   4.,   6.,   8.,  10.,  12.,  14.,  16.,  18.],
           [  3.,   6.,   9.,  12.,  15.,  18.,  21.,  24.,  27.],
           [  4.,   8.,  12.,  16.,  20.,  24.,  28.,  32.,  36.],
           [  5.,  10.,  15.,  20.,  25.,  30.,  35.,  40.,  45.],
           [  6.,  12.,  18.,  24.,  30.,  36.,  42.,  48.,  54.],
           [  7.,  14.,  21.,  28.,  35.,  42.,  49.,  56.,  63.],
           [  8.,  16.,  24.,  32.,  40.,  48.,  56.,  64.,  72.],
           [  9.,  18.,  27.,  36.,  45.,  54.,  63.,  72.,  81.]])