NumPy: 字符串操作

难度:⭐⭐ | 关键词np.char, np.strings, add, multiply, replace, split, join, find, upper, lower, strip

Charlie 有 100K 条产品描述需统一格式:strip 前后空白、首字母大写、替换缩写。Python 循环跑了 5 秒,np.char 只需 0.1 秒——"字符串也能向量化!"


1. 你将学到


2. np.char 全览

np.char 是 NumPy 提供的向量化字符串操作模块,对 str_object 类型的数组逐元素执行字符串函数,无需 Python 循环。

(1) 文本清洗流程

100%
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 表示未找到,而非抛出异常——这和 Python str.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,后续处理通常需要列表推导提取。

📖 小节


📝 作业

(1) np.char 处理邮箱

  1. 创建包含 10 个邮箱的数组:['ALICE@Gmail.com', 'BOB@yahoo.COM', ...]
  2. np.char 将所有邮箱转为小写
  3. np.char.replace@gmail.com 替换为 @gmail.com(统一格式),将 @yahoo.com 替换为 @yahoo.com
  4. np.char.split 提取用户名部分(@ 前的部分),打印结果

(2) 文本清洗流水线

  1. 创建包含 20 条模拟用户输入的数组(含前后空白、大小写混乱、缩写)
  2. 依次执行:strip → lower → replace(至少 3 个替换规则)→ title
  3. 统计清洗后包含特定关键词的条目数量
  4. 将结果存入结构化数组(至少 3 个字段)

(3) np.char vs 列表推导速度

  1. 生成 100_000 条随机字符串数组(如 ' item_XXX ' 格式,XXX 为随机数)
  2. 分别用 np.char.strip[s.strip() for s in arr] 处理
  3. time.perf_counter 计时,打印两者耗时和加速比
  4. 再测试 np.char.upper vs [s.upper() for s in arr],对比结果
Web-Tutorial.com

Web-Tutorial 技术团队

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

100%

🙏 帮我们做得更好

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

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