NumPy: 逐元素运算
1. 你将学到
- ❶ 算术运算 —
+ - * / // % **及对应函数 - ❷ 比较运算 —
> < >= <= == !=生成布尔掩码 - ❸ 逻辑运算 —
& | ^ ~与np.logical_and等 - ❹ 向量化 vs 循环 — 性能对比与原理
- ❺ ufunc 入门 — 通用函数的概念与属性
2. 故事
Bob 用 for 循环计算 1 million 个数的平方,耗时 0.3 秒。Alice 只写了一行 a ** 2,3 毫秒就完成了——
"NumPy 算术不是 Python 循环,是 C 层 ufunc 批量操作,快 100 倍。"
Bob 追问:"ufunc 是什么?"Alice 打开终端,输入 np.add 的文档——一切从逐元素运算开始。
3. 知识点
(1) 算术运算符与函数
NumPy 的算术运算作用于数组的每个元素,自动广播形状:
PYTHON
import numpy as np
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])
print(a + b) # [11 22 33 44]
print(a * b) # [10 40 90 160]
print(a ** 2) # [ 1 4 9 16]
print(b // a) # [10 10 10 10]
print(b % a) # [0 0 0 0]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
每个运算符都对应一个 NumPy 函数:
| 运算符 | 函数 | 说明 |
|---|---|---|
+ |
np.add |
加法 |
- |
np.subtract |
减法 |
* |
np.multiply |
乘法 |
/ |
np.true_divide |
真除法 |
// |
np.floor_divide |
整除 |
% |
np.mod |
取模 |
** |
np.power |
幂运算 |
(2) 比较运算
比较运算逐元素执行,返回布尔数组(掩码):
PYTHON
a = np.array([3, 7, 1, 9, 5])
print(a > 4) # [False True False True True]
print(a == 7) # [False True False False False]
print((a >= 3) & (a <= 7)) # [ True True False False True]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
| 运算符 | 函数 |
|---|---|
> |
np.greater |
< |
np.less |
>= |
np.greater_equal |
<= |
np.less_equal |
== |
np.equal |
!= |
np.not_equal |
(3) 逻辑运算
对布尔数组的逐元素逻辑组合,使用位运算符 & | ^ ~,而非 and or not:
PYTHON
a = np.array([True, True, False, False])
b = np.array([True, False, True, False])
print(a & b) # [ True False False False]
print(a | b) # [ True True True False]
print(~a) # [False False True True]
print(np.logical_and(a, b)) # [ True False False False]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
| 位运算符 | 函数 | 说明 |
|---|---|---|
& |
np.logical_and |
逻辑与 |
| |
np.logical_or |
逻辑或 |
^ |
np.logical_xor |
逻辑异或 |
~ |
np.logical_not |
逻辑非 |
⚠️ 注意: Python 关键字
and / or / not 只对标量生效,对数组会抛出异常,必须用 & | ~。
(4) ufunc 概念
ufunc(universal function)是 NumPy 对逐元素运算的底层封装:
- 每个 ufunc 是一个 C 层循环,对输入数组的每个元素执行同一操作
- 支持广播、类型提升、输出缓冲
- 拥有
.types、.nin、.nout等属性
PYTHON
print(np.add.nin) # 2 (number of inputs)
print(np.add.nout) # 1 (number of outputs)
print(np.add.types) # list of supported type signatures
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
(5) 错误处理 — np.seterr
浮点异常(除零、溢出等)的默认行为:
PYTHON
old = np.seterr(divide='warn', invalid='warn')
a = np.array([1.0, 0.0])
b = np.array([0.0, 0.0])
print(a / b) # [inf nan] + RuntimeWarning
np.seterr(**old) # restore
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
np.seterr 模式:
| 模式 | 行为 |
|---|---|
'ignore' |
静默,不警告 |
'warn' |
打印 RuntimeWarning(默认) |
'raise' |
抛出 FloatingPointError |
'call' |
调用回调函数 |
'print' |
打印到 stdout |
'log' |
记录到日志对象 |
4. 对比表格
(1) 运算符 vs 函数
| 运算符 | 等价函数 | 示例 |
|---|---|---|
a + b |
np.add(a, b) |
逐元素加 |
a * b |
np.multiply(a, b) |
逐元素乘 |
a ** 2 |
np.power(a, 2) |
逐元素幂 |
a > 3 |
np.greater(a, 3) |
逐元素比较 |
(2) 向量化 vs 循环
| 维度 | Python for 循环 | NumPy 向量化 |
|---|---|---|
| 执行层 | Python 解释器 | C 层 ufunc |
| 速度 | 慢(~100x) | 快 |
| 语法 | for i in range(n): ... |
a + b |
| 类型安全 | 手动检查 | 自动类型提升 |
(3) ufunc vs Python 函数
| 特性 | Python sum() |
np.add.reduce() |
|---|---|---|
| 输入 | 可迭代 | 数组 |
| 类型 | 动态 | 固定 dtype |
| 广播 | 无 | 有 |
| 速度 | 慢 | 快 |
(4) 错误处理模式
| 场景 | 'ignore' |
'warn' |
'raise' |
|---|---|---|---|
1/0 |
inf |
inf + 警告 |
异常 |
0/0 |
nan |
nan + 警告 |
异常 |
| 溢出 | 回绕 | 回绕 + 警告 | 异常 |
5. 原理图解
graph LR
A[Input Arrays] --> B[Broadcasting]
B --> C[Type Casting]
C --> D[C-level Elementwise Loop]
D --> E[Output Array]
D --> F[Error Flags]
F --> G[np.seterr Handler]
style A fill:#e1f5fe
style D fill:#fff9c4
style E fill:#c8e6c9
style G fill:#ffccbc
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
6. 示例
▶ 示例: 1:算术运算(难度⭐)
PYTHON
import numpy as np
a = np.array([2, 4, 6, 8])
b = np.array([1, 3, 5, 7])
print("a + b =", a + b) # [ 3 7 11 15]
print("a - b =", a - b) # [1 1 1 1]
print("a * b =", a * b) # [ 2 12 30 56]
print("a / b =", a / b) # [2. 1.33 1.2 1.14]
print("a // b =", a // b) # [2 1 1 1]
print("a % b =", a % b) # [0 1 1 1]
print("a ** 3 =", a ** 3) # [ 8 64 216 512]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
▶ 示例: 2:比较生成掩码(难度⭐)
PYTHON
import numpy as np
scores = np.array([55, 82, 91, 47, 73, 100])
pass_mask = scores >= 60
print("Pass mask:", pass_mask) # [False True True False True True]
print("Pass scores:", scores[pass_mask]) # [82 91 73 100]
excellent = scores >= 90
print("Excellent:", scores[excellent]) # [ 91 100]
mid_range = (scores >= 60) & (scores < 90)
print("Mid-range:", scores[mid_range]) # [82 73]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
▶ 示例: 3:逻辑组合(难度⭐⭐)
PYTHON
import numpy as np
data = np.array([12, -3, 0, 7, -8, 15])
positive = data > 0
nonzero = data != 0
print("Positive AND nonzero:", positive & nonzero)
# [ True False False True False True]
print("Positive OR nonzero:", positive | nonzero)
# [ True True False True True True]
print("NOT positive:", ~positive)
# [False True True False True False]
print("XOR:", positive ^ nonzero)
# [False True False False True False]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
▶ 示例: 4:向量化 vs 循环速度对比(难度⭐⭐)
PYTHON
import numpy as np
import time
n = 1_000_000
a = np.random.rand(n)
b = np.random.rand(n)
# --- for loop ---
start = time.perf_counter()
c_loop = np.empty(n)
for i in range(n):
c_loop[i] = a[i] + b[i]
loop_time = time.perf_counter() - start
# --- vectorized ---
start = time.perf_counter()
c_vec = a + b
vec_time = time.perf_counter() - start
print(f"Loop: {loop_time:.4f} s")
print(f"Vectorized: {vec_time:.6f} s")
print(f"Speedup: {loop_time / vec_time:.0f}x")
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
▶ 示例: 5:错误处理(难度⭐⭐)
PYTHON
import numpy as np
old = np.seterr(divide='ignore', invalid='ignore')
a = np.array([1.0, -1.0, 0.0, 0.0])
b = np.array([0.0, 0.0, 0.0, 1.0])
print("a / b =", a / b) # [ inf -inf nan 0.]
print("np.inf type:", type(np.inf)) # <class 'float'>
# detect special values
print("Is inf:", np.isinf(a / b)) # [ True True False False]
print("Is nan:", np.isnan(a / b)) # [False False True False]
print("Is finite:", np.isfinite(a / b)) # [False False False True]
np.seterr(**old)
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
▶ 示例: 6:综合 — 温度转换与 BMI 与成绩等级(难度⭐⭐⭐)
PYTHON
import numpy as np
# --- Temperature: Fahrenheit -> Celsius ---
fahrenheit = np.array([32, 68, 98.6, 212])
celsius = (fahrenheit - 32) * 5 / 9
print("F:", fahrenheit)
print("C:", np.round(celsius, 1))
# [ 0. 20. 37. 100.]
# --- BMI calculation ---
height_m = np.array([1.75, 1.60, 1.82, 1.55])
weight_kg = np.array([70, 55, 90, 48])
bmi = weight_kg / (height_m ** 2)
print("BMI:", np.round(bmi, 1)) # [22.9 21.5 27.2 20. ]
# BMI category
underweight = bmi < 18.5
normal = (bmi >= 18.5) & (bmi < 25)
overweight = bmi >= 25
print("Underweight:", underweight) # [False False False False]
print("Normal:", normal) # [ True True False True]
print("Overweight:", overweight) # [False False True False]
# --- Grade classification ---
scores = np.array([45, 62, 78, 91, 55, 88, 33, 70])
grade_A = scores >= 85
grade_B = (scores >= 70) & (scores < 85)
grade_C = (scores >= 60) & (scores < 70)
grade_D = scores < 60
print("A:", scores[grade_A]) # [91 88]
print("B:", scores[grade_B]) # [78 70]
print("C:", scores[grade_C]) # [62]
print("D:", scores[grade_D]) # [45 55 33]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
❓ 常见问题
Q 为什么用
np.add 而不是 +?A 效果完全相同,
+ 底层就是调用 np.add。显式使用函数的场景:需要指定 dtype、out、where 等参数时,或需要传递 ufunc 对象作为回调时。Q 除零会报错吗?
A 不会抛异常。浮点除零产生
inf 或 -inf,0/0 产生 nan,同时根据 np.seterr 设置决定是否打印警告。整数除零会抛出 ZeroDivisionError。Q 向量化一定比循环快吗?
A 对于大数组的简单运算,向量化通常快 50~200 倍。但极小数组(<100 元素)因 ufunc 调用开销,可能比循环还慢;复杂逻辑有时也需权衡可读性。
Q ufunc 是什么?
A universal function 的缩写,是对逐元素运算的 C 层封装。每个 ufunc 自动处理广播、类型提升和输出缓冲,是 NumPy 向量化的底层引擎。
Q 如何处理 NaN 和 Inf?
A 用
np.isnan() 检测 NaN,np.isinf() 检测 Inf,np.isfinite() 检测有限值。用 np.nan_to_num() 将 NaN/Inf 替换为指定值,或用布尔掩码 ~np.isnan(a) 过滤。Q 为什么不能用
and / or 组合布尔数组?A Python 的
and / or 只评估单个标量的真假值,对数组会抛出 ValueError: The truth value of an array with more than one element is ambiguous。必须用 & / | 并加括号。📖 小节
- 算术运算符
+ - * / // % **逐元素执行,各有对应np.xxx函数 - 比较运算生成布尔掩码,用于条件筛选
- 逻辑运算用
& | ^ ~,不用and or not;也可用np.logical_and等 - 向量化比 Python 循环快 50~200 倍,核心原因是 C 层 ufunc
- ufunc 是 NumPy 逐元素运算的底层机制,支持广播与类型提升
np.seterr控制浮点异常行为;np.isnan/np.isinf处理特殊值
📝 作业
-
基础题(难度⭐):创建数组
x = np.linspace(-5, 5, 100),用向量化方式计算 sigmoid 函数1 / (1 + np.exp(-x)),并统计结果中大于 0.5 的元素个数。 -
基础题(难度⭐):用
for循环和向量化两种方式分别计算np.sin(x)对 100 万个随机数的结果,对比耗时并计算加速比。 -
进阶题(难度⭐⭐):实现 ReLU 函数
max(0, x)的向量化版本:输入x = np.linspace(-3, 3, 20),输出将负数置零后的数组。分别用布尔掩码x * (x > 0)和np.maximum(0, x)两种方式实现。