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

通用函数(universal function,简称 ufunc)就是对数组逐元素应用数学运算的函数。它们由 C 语言实现,一次性遍历整个数组,比 Python 循环快几十上百倍。本章讲透:一元/二元 ufunc 有哪些、nan 陷阱、out= 省内存、以及"向量化思维"。

常用一元 ufunc(一个数组 → 一个数组)

import numpy as np

arr = np.array([-1, 0, 1, 2, 4])

np.abs(arr)      # [1 0 1 2 4]           绝对值
np.sqrt(arr)     # [nan 0. 1. 1.414 2.]  开平方(负数→nan)
np.square(arr)   # [1 0 1 4 16]          平方
np.exp(arr)      # 指数 e^x
np.log(arr)      # 自然对数(0→-inf,负数→nan)
np.log2(arr)     # 以 2 为底
np.log10(arr)    # 以 10 为底
np.sin(arr)      # 三角:sin / cos / tan
np.arcsin(arr)   # 反三角
np.ceil(arr)     # 向上取整
np.floor(arr)    # 向下取整
np.rint(arr)     # 四舍五入
np.sign(arr)     # 符号:-1 / 0 / 1
np.isfinite(arr) # 是否有限(识别 inf/nan)
np.round(arr, 2) # 四舍五入到 2 位小数

常用二元 ufunc(两个数组 → 一个数组)

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

np.add(a, b)        # [5 7 9]       加法(等价 a + b)
np.subtract(a, b)   # [-3 -3 -3]    减法
np.multiply(a, b)   # [4 10 18]     乘法
np.divide(a, b)     # [0.25 0.4 0.5]  除法
np.power(a, b)      # [1 32 729]    乘方
np.maximum(a, b)    # [4 5 6]       逐元素取较大
np.minimum(a, b)    # [1 2 3]       逐元素取较小
np.mod(a, b)        # 取余
np.hypot(a, b)      # sqrt(a²+b²)   勾股

💡 很多符号运算(+、*、>)底层就是在调用这些 ufunc(np.add、np.multiply、np.greater)——运算符只是语法糖。

⚠️ 灵魂警告:不合法输入返回 nan/inf,不抛异常!

arr = np.array([-1, 0, 4])

np.sqrt(arr)   # [nan 0. 2.]  负数的平方根 → nan(只给 RuntimeWarning)
np.log(arr)    # [nan -inf 1.386]  0 → -inf,负数 → nan

这些 nan / inf 会污染后续所有计算:

np.mean([1.0, 2.0, np.nan])   # nan  → 均值也变 nan!

对策:做完 ufunc 后检查 + 处理:

result = np.log(arr)

np.isnan(result).any()    # 有 nan 吗?
np.isinf(result).any()    # 有 inf 吗?

# 过滤掉非法值
clean = result[np.isfinite(result)]   # 布尔索引(第 9 章)

out= 参数:省内存的就地写法

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

np.add(a, b, out=a)   # 把 a+b 写进 a,不新建数组
a                     # [5 7 9]

大数据重复运算时用 out= 省掉临时数组分配。

为什么这么快?(向量化思维)

ufunc 直接在 C 层面遍历连续内存,没有 Python 循环的解释开销。实测对比:

import time

arr = np.random.default_rng().random(1_000_000)

# ufunc 版本
t0 = time.time()
np.sqrt(arr)
print(f"ufunc:        {time.time()-t0:.5f}s")   # 毫秒级

# Python 循环版本
t0 = time.time()
[float(x) ** 0.5 for x in arr]
print(f"Python 循环:  {time.time()-t0:.5f}s")   # 慢 50~100 倍

能用 ufunc 就绝不用 Python 循环,这是写高效 NumPy 代码的第一原则。

常见"以为要循环,其实有现成 ufunc"的场景

想做的事 别用 该用
每个数平方 [x**2 for x in arr] np.square(arr)
每个数取对数 循环 + math.log np.log(arr)
两列逐元素取大 循环 np.maximum(c1, c2)
整列标准化 循环 (arr - arr.mean()) / arr.std()
逐元素条件取值 循环 + if np.where(mask, x, y)(第 9 章)

小结

  1. ufunc = 逐元素运算,C 实现,比 Python 循环快 50~100 倍
  2. 一元:np.abs / sqrt / exp / log / sin / ceil / floor…
  3. 二元:np.add / subtract / multiply / divide / power / maximum…
  4. ⚠️ 非法输入返回 nan/inf 不报错,会污染后续计算——做完检查 np.isnan
  5. 向量化思维:能用 ufunc 就不写循环;分支逻辑用 np.where

下一章:把整组数"算总账"(统计与聚合)。


上一篇:第 5 章 形状操作 | 下一篇:第 7 章 统计与聚合