Python进阶编程:编写更高效、优雅的Python代码
上QQ阅读APP看书,第一时间看更新

1.4.5 大型数组运算

在实际应用中,我们经常需要在大数据集(比如数组或网格)上执行运算。

涉及数组的重量级运算,可以使用NumPy库。NumPy库的一个主要特征是它会给Python提供一个数组对象,其相比于标准的Python列表而言更适合用来做数学运算。下面通过示例查看标准的Python列表对象和NumPy库中的数组对象之间的使用差别:


x = [1, 2, 3, 4]
y = [5, 6, 7, 8]

print(f'{x} * 2 is: {x * 2}')
# print(x + 10)

print(f'{x} + {y} = {x + y}')

import numpy as np
ax = np.array([1, 2, 3, 4])
ay = np.array([5, 6, 7, 8])

print(f'{ax} * 2 = {ax * 2}')
print(f'{ax} + 10 = {ax + 10}')
print(f'{ax} + {ay} = {ax + ay}')
print(f'{ax} * {ay} = {ax * ay}')

执行py文件,输出结果如下:


[1, 2, 3, 4] * 2 is: [1, 2, 3, 4, 1, 2, 3, 4]
[1, 2, 3, 4] + [5, 6, 7, 8] = [1, 2, 3, 4, 5, 6, 7, 8]
[1 2 3 4] * 2 = [2 4 6 8]
[1 2 3 4] + 10 = [11 12 13 14]
[1 2 3 4] + [5 6 7 8] = [6  8 10 12]
[1 2 3 4] * [5 6 7 8] = [5 12 21 32]

两种方案中数组的基本数学运算结果并不相同。NumPy库中的标量运算(如ax*2或ax+10)会作用在每一个元素上。当两个操作数都是数组的时候,执行元素对等位置计算,并最终生成一个新的数组。

对整个数组中的所有元素同时执行数学运算,可以使得作用在整个数组上的函数运算简单又快速。计算多项式的值的示例如下:


def f(px):
    return 3 * px ** 2 - 2 * px + 7

print(f'f(ax) = {f(ax)}')

执行py文件,输出结果如下:


f(ax) = [ 8 15 28 47]

NumPy还为数组操作提供了大量的通用函数,这些函数可以作为math模块中类似函数的替代,示例如下:


print(f'np.sqrt({ax}) = {np.sqrt(ax)}')
print(f'np.cos({ax}) = {np.cos(ax)}')

执行py文件,输出结果如下:


np.sqrt([1 2 3 4]) = [1.  1.41421356 1.73205081 2.        ]
np.cos([1 2 3 4]) = [0.54030231  -0.41614684  -0.9899925  -0.65364362]

使用这些通用函数要比循环数组并使用math模块中的函数执行计算快得多,因此应尽量选择NumPy的数组方案。

在底层实现中,NumPy数组使用了C或者Fortran语言的机制分配内存。也就是说,NumPy是一个非常大、连续并由同类型数据组成的内存区域,所以可以构造一个比普通Python列表大得多的数组。如构造一个10 000×10 000的浮点数二维网格,示例如下:


grid = np.zeros(shape=(10000,10000), dtype=float)
print(f'np.zeros(shape=(10000,10000), dtype=float) result: \n{grid}')

执行py文件,输出结果如下:


np.zeros(shape=(10000,10000), dtype=float) result: 
[[0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 ...
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]]

所有的普通操作还是会同时作用在所有元素上:


grid += 10
print(f'grid + 10 result:\n{grid}')
print(f'np.sin(grid) result:\n{np.sin(grid)}')

执行py文件,输出结果如下:


grid + 10 result:
[[10. 10. 10. ... 10. 10. 10.]
 [10. 10. 10. ... 10. 10. 10.]
 [10. 10. 10. ... 10. 10. 10.]
 ...
 [10. 10. 10. ... 10. 10. 10.]
 [10. 10. 10. ... 10. 10. 10.]
 [10. 10. 10. ... 10. 10. 10.]]
np.sin(grid) result:
[[-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111
  -0.54402111]
 [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111
  -0.54402111]
 [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111
  -0.54402111]
 ...
 [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111
  -0.54402111]
 [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111
  -0.54402111]
 [-0.54402111 -0.54402111 -0.54402111 ... -0.54402111 -0.54402111
  -0.54402111]]

对于NumPy数组,需要特别注意它扩展Python列表的索引功能——特别是对于多维数组。为了演示多维数组的索引功能,首先构造一个简单的二维数组:


a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
print(f'a is:\n{a}')
print(f'a[1] is:\n{a[1]}')
print(f'a[:,1] is:\n{a[:,1]}')
print(f'a[1:3, 1:3] is:\n{a[1:3, 1:3]}')

a[1:3, 1:3] += 10
print(f'a is:\n{a}')
print(f'a + [100, 101, 102, 103] is:\n{a + [100, 101, 102, 103]}')
print(f'np.where(a < 10, a, 10) is:\n{np.where(a < 10, a, 10)}')

执行py文件,输出结果如下:


a is:
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
a[1] is:
[5 6 7 8]
a[:,1] is:
[ 2  6 10]
a[1:3, 1:3] is:
[[ 6  7]
 [10 11]]
a is:
[[ 1  2  3  4]
 [ 5 16 17  8]
 [ 9 20 21 12]]
a + [100, 101, 102, 103] is:
[[101 103 105 107]
 [105 117 119 111]
 [109 121 123 115]]
np.where(a < 10, a, 10) is:
[[ 1  2  3  4]
 [ 5 10 10  8]
 [ 9 10 10 10]]

NumPy是Python中很多科学与工程库的基础,同时也是被广泛使用的最大、最复杂的模块之一。NumPy通过一些简单程序能完成有趣的事情。

通常,导入NumPy库的时候,我们会使用语句import numpy as np。这样就不用在程序中一遍遍地输入numpy,只需要输入np即可,节省了输入时间。