NumPy: 字符串操作
难度:⭐⭐ | 关键词:
np.char,np.strings,add,multiply,replace,split,join,find,upper,lower,strip
Charlie 有 100K 条产品描述需统一格式:strip 前后空白、首字母大写、替换缩写。Python 循环跑了 5 秒,np.char 只需 0.1 秒——"字符串也能向量化!"
1. 你将学到
- ❶
np.char向量化字符串操作原理 - ❷ 拼接、重复、查找、替换等常见操作
- ❸ 查找与计数:find / count / startswith / endswith
- ❹ 大小写转换与空白处理:upper / lower / strip
- ❺
np.char与 Pythonstr的对比
2. np.char 全览
np.char 是 NumPy 提供的向量化字符串操作模块,对 str_ 或 object 类型的数组逐元素执行字符串函数,无需 Python 循环。
(1) 文本清洗流程
graph LR
A["原始文本数组<br>前后空白/大小写混乱"] --> B["strip<br>去除空白"]
B --> C["lower / upper<br>统一大小写"]
C --> D["replace<br>替换缩写/特殊字符"]
D --> E["split / join<br>分割与重组"]
E --> F["结构化数组<br>干净可分析"]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(2) np.char 与 Python str 对应
Python str 方法 |
np.char 函数 |
说明 |
|---|---|---|
s + t |
np.char.add(a, b) |
拼接 |
s * n |
np.char.multiply(a, n) |
重复 |
s.replace(old, new) |
np.char.replace(a, old, new) |
替换 |
s.split(sep) |
np.char.split(a, sep) |
分割(返回 object) |
sep.join(list) |
np.char.join(sep, a) |
用分隔符拼接字符 |
s.find(sub) |
np.char.find(a, sub) |
查找子串位置 |
s.count(sub) |
np.char.count(a, sub) |
计数子串 |
s.upper() |
np.char.upper(a) |
转大写 |
s.lower() |
np.char.lower(a) |
转小写 |
s.title() |
np.char.title(a) |
首字母大写 |
s.strip() |
np.char.strip(a) |
去除两端空白 |
s.lstrip() |
np.char.lstrip(a) |
去除左端空白 |
s.rstrip() |
np.char.rstrip(a) |
去除右端空白 |
s.startswith(p) |
np.char.startswith(a, p) |
前缀判断 |
s.endswith(p) |
np.char.endswith(a, p) |
后缀判断 |
s.center(w) |
np.char.center(a, w) |
居中填充 |
s.ljust(w) |
np.char.ljust(a, w) |
左对齐填充 |
s.rjust(w) |
np.char.rjust(a, w) |
右对齐填充 |
s.zfill(w) |
np.char.zfill(a, w) |
左侧补零 |
s.encode() |
np.char.encode(a, enc) |
编码 |
s.decode() |
np.char.decode(a, enc) |
解码 |
(3) np.char vs Python str
| 特性 | Python str 循环 |
np.char |
|---|---|---|
| 逐元素操作 | for 循环 / 列表推导 | 一次函数调用 |
| 返回类型 | list[str] | ndarray[str_ / object] |
| 广播 | 手动对齐 | 自动广播 |
| 性能(100K 条) | ~5s | ~0.1s |
| 正则支持 | re 模块 |
不直接支持 |
| 输出 dtype | — | str_(Unicode)或 object |
(4) 处理方式对比
| 方式 | 代码示例 | 性能 | 可读性 |
|---|---|---|---|
| for 循环 | [s.strip() for s in arr] |
慢 | 一般 |
| 列表推导 | [s.strip().title() for s in arr] |
慢 | 好 |
| map | list(map(str.strip, arr)) |
中 | 一般 |
| np.char | np.char.strip(arr) |
快 | 好 |
3. 拼接与重复
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:拼接 / 重复(难度⭐)
PYTHON
import numpy as np
a = np.array(['Hello', 'Good', 'Fine'])
b = np.array([' World', ' Bye', ' Day'])
print(np.char.add(a, b))
# ['Hello World' 'Good Bye' 'Fine Day']
print(np.char.multiply(a, 3))
# ['HelloHelloHello' 'GoodGoodGood' 'FineFineFine']
prefix = np.array(['item_'])
ids = np.array(['001', '002', '003'])
print(np.char.add(prefix, ids))
# ['item_001' 'item_002' 'item_003']
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
np.char.add支持广播:一个标量字符串可与整个数组拼接。
4. 查找与替换
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:查找 / 替换(难度⭐⭐)
PYTHON
import numpy as np
desc = np.array([
'USB-C Cable 2m',
'HDMI Cable 1.5m',
'USB-A Adapter',
'USB-C Hub 4-Port'
])
print(np.char.find(desc, 'USB'))
# [ 0 -1 0 0]
print(np.char.count(desc, 'USB'))
# [1 0 1 1]
print(np.char.replace(desc, 'USB-C', 'Type-C'))
# ['Type-C Cable 2m' 'HDMI Cable 1.5m' 'USB-A Adapter' 'Type-C Hub 4-Port']
print(np.char.startswith(desc, 'USB'))
# [ True False True True]
print(np.char.endswith(desc, 'm'))
# [ True True False False]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(1) 常见操作速查
| 操作 | 函数 | 返回类型 | 说明 |
|---|---|---|---|
| 查找位置 | np.char.find(a, sub) |
int 数组 | 找不到返回 -1 |
| 计数子串 | np.char.count(a, sub) |
int 数组 | 统计出现次数 |
| 前缀判断 | np.char.startswith(a, p) |
bool 数组 | 是否以 p 开头 |
| 后缀判断 | np.char.endswith(a, p) |
bool 数组 | 是否以 p 结尾 |
| 替换 | np.char.replace(a, old, new) |
str 数组 | 全部替换 |
| 查找(从右) | np.char.rfind(a, sub) |
int 数组 | 从右侧查找位置 |
np.char.find返回 -1 表示未找到,而非抛出异常——这和 Pythonstr.find行为一致。
5. 分割与拼接
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:分割 / 拼接(难度⭐⭐)
PYTHON
import numpy as np
names = np.array(['John-Doe', 'Jane-Smith', 'Bob-Lee'])
parts = np.char.split(names, sep='-')
print(parts)
# [list(['John', 'Doe']) list(['Jane', 'Smith']) list(['Bob', 'Lee'])]
first_names = np.array([p[0] for p in parts])
print(first_names)
# ['John' 'Jane' 'Bob']
chars = np.array(['a', 'b', 'c', 'd'])
print(np.char.join('-', chars))
# ['a' 'b' 'c' 'd']
words = np.array(['Hello World', 'NumPy Tutorial'])
print(np.char.replace(words, ' ', '_'))
# ['Hello_World' 'NumPy_Tutorial']
csv_fields = np.array(['name,age,city', 'Alice,30,NYC', 'Bob,25,LA'])
fields = np.char.split(csv_fields, sep=',')
print(fields)
# [list(['name', 'age', 'city']) list(['Alice', '30', 'NYC']) list(['Bob', '25', 'LA'])]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
np.char.split返回 object 数组(每个元素是 Python list),因为分割后子串数量可能不同。后续操作通常需要列表推导提取字段。
6. 大小写转换与空白处理
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:大小写 / 空白(难度⭐)
PYTHON
import numpy as np
messy = np.array([' hello ', 'WORLD', ' PyThOn '])
print(np.char.strip(messy))
# ['hello' 'WORLD' 'PyThOn']
print(np.char.lower(np.char.strip(messy)))
# ['hello' 'world' 'python']
print(np.char.title(np.char.strip(messy)))
# ['Hello' 'World' 'Python']
print(np.char.upper(messy))
# [' HELLO ' 'WORLD' ' PYTHON ']
print(np.char.lstrip(messy))
# ['hello ' 'WORLD' 'PyThOn ']
print(np.char.rstrip(messy))
# [' hello' 'WORLD' ' PyThOn']
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(1) 大小写与空白函数一览
| 函数 | 功能 | 示例输入 | 示例输出 |
|---|---|---|---|
upper |
全部大写 | 'hello' |
'HELLO' |
lower |
全部小写 | 'WORLD' |
'world' |
title |
首字母大写 | 'hello world' |
'Hello World' |
capitalize |
首字母大写其余小写 | 'hELLO' |
'Hello' |
swapcase |
大小写互换 | 'HeLLo' |
'hEllO' |
strip |
去两端空白 | ' hi ' |
'hi' |
lstrip |
去左端空白 | ' hi ' |
'hi ' |
rstrip |
去右端空白 | ' hi ' |
' hi' |
7. 填充与编码
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:填充 / 编码(难度⭐⭐)
PYTHON
import numpy as np
nums = np.array(['42', '7', '123'])
print(np.char.zfill(nums, 5))
# ['00042' '00007' '00123']
print(np.char.center(nums, 7, fillchar='_'))
# ['__42___' '__7____' '_123___']
print(np.char.rjust(nums, 6))
# [' 42' ' 7' ' 123']
text = np.array(['hello', 'world'])
encoded = np.char.encode(text, encoding='utf-8')
print(encoded)
# [b'hello' b'world']
decoded = np.char.decode(encoded, encoding='utf-8')
print(decoded)
# ['hello' 'world']
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
8. np.char vs np.strings(NumPy 2.x)
NumPy 2.0 引入了 np.strings 模块,作为 np.char 的现代替代。两者 API 几乎一致,但 np.strings 在底层实现和扩展性上有改进。
(1) np.char vs np.strings
| 特性 | np.char |
np.strings (2.x) |
|---|---|---|
| 引入版本 | 1.x 早期 | 2.0+ |
| 函数命名 | 与 Python str 一致 | 与 Python str 一致 |
| 底层实现 | 逐元素 Python 回调 | C 层优化(逐步迁移) |
| 输出 dtype | str_ / object |
StringDType(新 dtype) |
| 扩展性 | 有限 | 可添加新 ufunc |
| 向后兼容 | — | 兼容 np.char 接口 |
| 推荐度 | 当前主流 | 未来方向 |
PYTHON
import numpy as np
print(np.__version__)
a = np.array(['hello', 'world'])
if hasattr(np, 'strings'):
print(np.strings.upper(a))
else:
print(np.char.upper(a))
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
如果你使用 NumPy 2.x,推荐优先使用
np.strings;1.x 用户继续使用np.char,API 兼容。
9. 综合示例:批量清洗 1000 条用户输入
Charlie 用
np.char批量清洗用户提交的产品描述,统一格式后存入结构化数组。
PYTHON
import numpy as np
rng = np.random.default_rng(42)
raw_names = np.array([
' USB-C cable ', 'HDMI CABLE 1.5m', ' usb-a adapter',
'Type-C HUB ', ' HDMI cable 2m ', 'USB-C ADAPTER',
' usb-c hub 4-port', 'HDMI ADAPTER ', ' type-c cable 2m',
'USB-A CABLE 1m '
])
N = 1000
raw = rng.choice(raw_names, size=N)
print(f"Raw samples (first 5):")
for i in range(5):
print(f" [{i}] '{raw[i]}'")
# ========================================
# Part 1: 去除空白 + 统一小写
# ========================================
print("\n=== Part 1: Strip + Lower ===\n")
step1 = np.char.strip(raw)
step2 = np.char.lower(step1)
for i in range(5):
print(f" [{i}] '{step2[i]}'")
# ========================================
# Part 2: 替换缩写
# ========================================
print("\n=== Part 2: Replace Abbreviations ===\n")
step3 = np.char.replace(step2, 'usb-c', 'USB-C')
step3 = np.char.replace(step3, 'usb-a', 'USB-A')
step3 = np.char.replace(step3, 'type-c', 'USB-C')
step3 = np.char.replace(step3, 'hub', 'Hub')
for i in range(5):
print(f" [{i}] '{step3[i]}'")
# ========================================
# Part 3: 首字母大写
# ========================================
print("\n=== Part 3: Title Case ===\n")
step4 = np.char.title(step3)
for i in range(5):
print(f" [{i}] '{step4[i]}'")
# ========================================
# Part 4: 提取类别
# ========================================
print("\n=== Part 4: Extract Category ===\n")
is_cable = np.char.find(step4, 'Cable') >= 0
is_hub = np.char.find(step4, 'Hub') >= 0
is_adapter = np.char.find(step4, 'Adapter') >= 0
category = np.where(is_cable, 'Cable',
np.where(is_hub, 'Hub',
np.where(is_adapter, 'Adapter', 'Other')))
for i in range(5):
print(f" [{i}] '{step4[i]}' -> {category[i]}")
# ========================================
# Part 5: 存入结构化数组
# ========================================
print("\n=== Part 5: Structured Array ===\n")
dtype = np.dtype([
('clean_name', 'U40'),
('category', 'U10'),
('has_usb_c', '?'),
('has_hdmi', '?'),
])
result = np.empty(N, dtype=dtype)
result['clean_name'] = step4
result['category'] = category
result['has_usb_c'] = np.char.find(step4, 'USB-C') >= 0
result['has_hdmi'] = np.char.find(step4, 'HDMI') >= 0
print(f"Structured array (first 5):")
for i in range(5):
print(f" {result[i]}")
print(f"\nCategory counts:")
cats, counts = np.unique(result['category'], return_counts=True)
for c, n in zip(cats, counts):
print(f" {c}: {n}")
usb_c_count = result['has_usb_c'].sum()
hdmi_count = result['has_hdmi'].sum()
print(f"\nUSB-C products: {usb_c_count}")
print(f"HDMI products: {hdmi_count}")
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
❓ 常见问题
Q np.char 和 Python str 区别?
A
np.char 函数对整个数组逐元素执行字符串操作,自动广播,返回 ndarray;Python str 方法只能作用于单个字符串,处理数组需要循环或列表推导。Q 支持正则吗?
A
np.char 不直接支持正则表达式。需要正则时,用 np.vectorize 包装 re.sub / re.findall,或对结果数组使用 Python re 模块循环处理。pandas 的 Series.str 对正则支持更好。Q np.strings 和 np.char 区别?
A
np.strings 是 NumPy 2.0 引入的替代模块,API 与 np.char 几乎一致,但底层使用新的 StringDType 和 C 层优化,扩展性更好。1.x 用户只能用 np.char,2.x 推荐优先用 np.strings。Q 字符串数组能广播吗?
A 能。
np.char 函数支持广播——例如 np.char.add(np.array(['pre_']), np.array(['a', 'b', 'c'])) 会自动将标量字符串 'pre_' 广播到每个元素,返回 ['pre_a' 'pre_b' 'pre_c']。Q 为什么字符串操作比数值慢?
A 字符串长度不固定,无法像数值那样用固定步长连续内存直接计算。
np.char 底层仍逐元素调用 Python 字符串方法,只是省去了 Python 层循环开销,所以比数值向量化慢,但比纯 Python 循环快约 10~50 倍。Q np.char.split 为什么返回 object 数组?
A 因为不同字符串分割后子串数量可能不同,无法存入固定形状的 ndarray。每个元素是一个 Python list,后续处理通常需要列表推导提取。
📖 小节
np.char模块 — 对 str_/object 数组逐元素执行字符串操作,无需 Python 循环- 拼接/重复 —
add拼接、multiply重复,支持广播 - 查找/替换 —
find找位置、count计数、replace替换、startswith/endswith判断 - 分割/拼接 —
split分割返回 object 数组,join用分隔符拼接字符 - 大小写 —
upper/lower/title/capitalize/swapcase - 空白处理 —
strip/lstrip/rstrip去除空白 - 填充对齐 —
center/ljust/rjust/zfill - 编码/解码 —
encode/decode在字节与字符串间转换 np.strings— NumPy 2.x 的现代替代,API 兼容,底层优化
📝 作业
(1) np.char 处理邮箱
- 创建包含 10 个邮箱的数组:
['ALICE@Gmail.com', 'BOB@yahoo.COM', ...] - 用
np.char将所有邮箱转为小写 - 用
np.char.replace将@gmail.com替换为@gmail.com(统一格式),将@yahoo.com替换为@yahoo.com - 用
np.char.split提取用户名部分(@前的部分),打印结果
(2) 文本清洗流水线
- 创建包含 20 条模拟用户输入的数组(含前后空白、大小写混乱、缩写)
- 依次执行:strip → lower → replace(至少 3 个替换规则)→ title
- 统计清洗后包含特定关键词的条目数量
- 将结果存入结构化数组(至少 3 个字段)
(3) np.char vs 列表推导速度
- 生成 100_000 条随机字符串数组(如
' item_XXX '格式,XXX 为随机数) - 分别用
np.char.strip和[s.strip() for s in arr]处理 - 用
time.perf_counter计时,打印两者耗时和加速比 - 再测试
np.char.uppervs[s.upper() for s in arr],对比结果