本系列是《NumPy 速通》的配套章节。

NumPy 最大的魅力:数组可以直接做数学运算,而且是逐元素的(element-wise),不需要写循环。本章讲透四类运算:算术、标量、比较、* vs @,以及"就地运算"这个高效技巧。

逐元素算术运算

import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

a + b    # [5 7 9]     加法(对应位置相加)
a - b    # [-3 -3 -3]  减法
a * b    # [4 10 18]   乘法
a / b    # [0.25 0.4  0.5 ]  除法(结果是浮点)
a // b   # [0 0 0]     整除
a % b    # [1 2 3]     取余
a ** 2   # [1 4 9]     乘方
-a       # [-1 -2 -3]  取负

两个数组形状相同 → 对应位置逐个算,这就是"逐元素"(element-wise)。不需要写 for i in range(len(a))——这正是 NumPy 快的原因。

运算符 ↔ ufunc 对应表

运算符 对应 ufunc
+ np.add
- np.subtract
* np.multiply
/ np.divide
** np.power
> < == 等 np.greater 等

运算符是 ufunc 的"语法糖",二者等价。底层都是 C 实现的 ufunc(第 6 章详述)。

标量运算(广播的雏形)

一个数字和数组运算,会"广播"到每个元素(完整机制见第 8 章):

a + 10    # [11 12 13]  每个元素 +10
a * 2     # [2 4 6]
a / 0     # 警告 + [inf inf inf](除零得 inf,不抛异常!)

⚠️ 灵魂警告:NumPy 除以 0 不报错,而是得到 inf(无穷)并给个 RuntimeWarning。这在 Python 里是要抛 ZeroDivisionError 的。所以算完除法要检查是否有 inf / nan:

result = a / b
np.isinf(result).any()   # 是否有无穷
np.isnan(result).any()   # 是否有 NaN

比较运算:返回布尔数组

比较运算符逐元素返回 True / False,是筛选(第 9 章)的地基:

a > 2     # [False False  True]
a == 2    # [False  True False]
a != 2    # [ True False  True]

别用 == 比较浮点数组!

np.array([0.1 + 0.2]) == 0.3       # [False]  ⚠️ 浮点误差
np.allclose([0.1 + 0.2], [0.3])    # True     用 allclose!

浮点数的二进制表示有误差,比较浮点数组用 np.allclose(容忍微小误差),别用 ==。

⚠️ 两个"乘法":* vs @(新手最常踩的坑)

  • *:逐元素乘法(对应位置相乘,数组必须同形状)
  • @:矩阵乘法(行 × 列,线性代数里的点积)
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

A * B   # [[ 5 12] [21 32]]   逐元素:A[i,j]*B[i,j]
A @ B   # [[19 22] [43 50]]   矩阵:行×列求和
np.dot(A, B)   # 和 @ 等价

用错符号结果完全不同。想算线性代数的"矩阵乘法"(比如神经网络的前向传播 W @ x),必须用 @。

💡 注意维度要求:@ 要求"内维相等",(m, n) @ (n, k) → (m, k)。(3,2) @ (3,2) 会报错(2≠3),而 * 要求完全同形状。

就地运算:+= / *= 等(高效技巧)

a = np.array([1, 2, 3])

a += 10      # 就地加,不产生新数组(a 变成 [11 12 13])
a *= 2       # [22 24 26]
a[0] = 99    # 修改元素

a += 10 和 a = a + 10 的区别:

  • a += 10:原地修改 a,不分配新内存 → 大数据时快
  • a = a + 10:新建数组再重新绑定 → 多占一份内存

⚠️ 但注意:a += 10 是原地修改。如果 b = a,然后 a += 10,b 也跟着变(共享内存)。要避免就用 a = a + 10 或 a = a + 10(重新绑定)。

聚合运算(预览)

对整组数算总账(完整版见第 7 章):

np.sum(a)     # 总和
np.mean(a)    # 平均
np.max(a)     # 最大
np.min(a)     # 最小
np.prod(a)    # 连乘

小结

  1. 算术、比较都是逐元素的,不用写循环
  2. * 是逐元素,@ 是矩阵乘法——别混
  3. 除零得 inf 不报错,算完检查 inf/nan
  4. 浮点比较用 np.allclose
  5. 大数据循环运算用就地 += / *= 省内存

下一章:调整数组的形状。


上一篇:第 3 章 索引与切片 | 下一篇:第 5 章 形状操作