Python科学计算(第2版)
上QQ阅读APP看书,第一时间看更新

2.2 ufunc函数

与本节内容对应的Notebook为:02-numpy/numpy-200-ufunc.ipynb。

ufunc是universal function的缩写,它是一种能对数组的每个元素进行运算的函数。NumPy内置的许多ufunc函数都是用C语言实现的,因此它们的计算速度非常快。让我们先看一个例子:

    x = np.linspace(0, 2*np.pi, 10)
    y = np.sin(x)
    y
    array([  0.00000000e+00,   6.42787610e-01,   9.84807753e-01,
    8.66025404e-01,   3.42020143e-01,  -3.42020143e-01,
    -8.66025404e-01,  -9.84807753e-01,  -6.42787610e-01,
    -2.44929360e-16])

先用linspace()产生一个从0到2π的等差数组,然后将其传递给np.sin()函数计算每个元素的正弦值。由于np.sin()是一个ufunc函数,因此在其内部对数组x的每个元素进行循环,分别计算它们的正弦值,并返回一个保存各个计算结果的数组。运算之后数组x中的值并没有改变,而是新创建了一个数组来保存结果。也可以通过out参数指定保存计算结果的数组。因此如果希望直接在数组x中保存结果,可以将它传递给out参数:

    t = np.sin(x, out=x)
    t is x
    True

ufunc函数的返回值仍然是计算的结果,只不过它就是数组x。下面比较np.sin()和Python标准库的math.sin()的计算速度:

    import math
    
    x = [i * 0.001 for i in xrange(1000000)]
    
    def sin_math(x):
        for i, t in enumerate(x):
            x[i] = math.sin(t)
    
    def sin_numpy(x):
        np.sin(x, x)
    
    def sin_numpy_loop(x):
        for i, t in enumerate(x):
            x[i] = np.sin(t)
    
    xl = x[:]
    %time sin_math(x)
    
    xa = np.array(x)
    %time sin_numpy(xa)
    
    xl = x[:]
    %time sin_numpy_loop(x)
    Wall time: 302 ms
    Wall time: 30 ms
    Wall time: 1.28 s

可以看出,np.sin()比math.sin()快10倍多,这得益于np.sin()在C语言级别的循环计算。

列表推导式比循环更快

事实上,标准Python中有比for循环更快的方案:使用列表推导式x=[math.sin(t) fort in x]。但是列表推导式将产生一个新的列表,而不是直接修改原列表中的元素。

np.sin()同样也支持计算单个数值的正弦值。不过值得注意的是,对单个数值的计算,math.sin()则比np.sin()快很多。在Python级别进行循环时,np.sin()的计算速度只有math.sin()的1/6。这是因为:np.sin()为了同时支持数组和单个数值的计算,其C语言的内部实现要比math.sin()复杂很多。此外,对于单个数值的计算,np.sin()的返回值类型和math.sin()的不同,math.sin()返回的是Python的标准float类型,而np.sin()返回float64类型:

    type(math.sin(0.5))  type(np.sin(0.5))
    -------------------  -----------------
    float               numpy.float64    

通过下标运算获取的数组元素的类型为NumPy中定义的类型,将其转换为Python的标准类型还需要花费额外的时间。为了解决这个问题,数组提供了item()方法,它用来获取数组中的单个元素,并且直接返回标准的Python数值类型:

    a = np.arange(6.0).reshape(2, 3)
    print a.item(1, 2), type(a.item(1, 2)), type(a[1, 2])
    5.0 <type 'float'><type 'numpy.float64'>

通过上面的例子我们了解了如何最有效率地使用math模块和NumPy中的数学函数。由于它们各有优缺点,因此在导入时不建议使用import *全部载入,而是应该使用import numpy as np载入,这样可以根据需要选择合适的函数。