NumPy: 逐元素运算

1. 你将学到


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 对逐元素运算的底层封装:

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. 原理图解

100%
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。显式使用函数的场景:需要指定 dtypeoutwhere 等参数时,或需要传递 ufunc 对象作为回调时。
Q 除零会报错吗?
A 不会抛异常。浮点除零产生 inf-inf0/0 产生 nan,同时根据 np.seterr 设置决定是否打印警告。整数除零会抛出 ZeroDivisionError
Q 向量化一定比循环快吗?
A 对于大数组的简单运算,向量化通常快 50~200 倍。但极小数组(<100 元素)因 ufunc 调用开销,可能比循环还慢;复杂逻辑有时也需权衡可读性。
Q ufunc 是什么?
A universal function 的缩写,是对逐元素运算的 C 层封装。每个 ufunc 自动处理广播、类型提升和输出缓冲,是 NumPy 向量化的底层引擎。
Q 如何处理 NaN 和 Inf?
Anp.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。必须用 & / | 并加括号。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建数组 x = np.linspace(-5, 5, 100),用向量化方式计算 sigmoid 函数 1 / (1 + np.exp(-x)),并统计结果中大于 0.5 的元素个数。

  2. 基础题(难度⭐):用 for 循环和向量化两种方式分别计算 np.sin(x) 对 100 万个随机数的结果,对比耗时并计算加速比。

  3. 进阶题(难度⭐⭐):实现 ReLU 函数 max(0, x) 的向量化版本:输入 x = np.linspace(-3, 3, 20),输出将负数置零后的数组。分别用布尔掩码 x * (x > 0)np.maximum(0, x) 两种方式实现。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏