文本进阶:正则与新字符串类型
本教程共 54 篇 · 第 49 篇 · 更新于 2026-08-11 · 约 7 分钟阅读
本节目标:学会用正则表达式做文本匹配、提取与替换,并搞懂 pandas 3.0 的 StringDtype 新字符串类型与 object 的区别。
接 §48(文本数据 .str 访问器):上节讲了 .str 的日常方法,这节补两块硬核内容——正则表达式的三个主力方法,以及 pandas 3.0 全新默认的字符串类型 StringDtype。
正则三件套:contains、extract、replace
正则表达式(regex)是描述”字符串模式”的语言。比如 \d+ 表示”一个或多个数字”,[a-z]+ 表示”一串小写字母”。.str 访问器的方法大多支持正则,其中三个最常用。
contains:判断是否包含
str.contains 判断每个字符串是否匹配模式,返回布尔 Series。它和 Python 的 re.search 是同一套规则:字符串里任何位置匹配上,就算 True。
import pandas as pd
s = pd.Series(["订单A123", "订单B456", "无编号", None], dtype="str")
print(s.str.contains(r"\d"))
# 0 True
# 1 True
# 2 False
# 3 NaN
# dtype: boolean
两个参数要记住:
na=:缺失值怎么处理。默认返回 NaN,设成na=False就返回 False,方便直接当布尔索引用。case=False:忽略大小写。想同时匹配 “abc” 和 “ABC” 时很省事。
contains 还有两个”亲戚”:str.match 要求从字符串开头匹配,str.fullmatch 要求整个字符串完全匹配。严格程度排序:contains(任意位置)< match(开头)< fullmatch(整体)。判断”整行是否都是手机号”这类需求,fullmatch 最合适。
extract:提取匹配内容
str.extract 把匹配到的内容抠出来,正则需要用括号圈出”捕获组”。一组对应一列,多组返回 DataFrame:
import pandas as pd
s = pd.Series(["2024-01-15", "2024-02-20", "日期丢失"], dtype="str")
df = s.str.extract(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})")
print(df)
# year month day
# 0 2024 01 15
# 1 2024 02 20
# 2 NaN NaN NaN
(?P<year>...) 是命名捕获组,名字直接变成列名。没匹配上的行整行是 NaN——这正是清洗脏数据的好工具:一条字符串列,拆成干净的 year、month、day 三列。只有一个捕获组时,expand=False 返回 Series 而不是 DataFrame。
一个字符串里有多处匹配时,str.extract 只取第一处。想要全部,用 str.extractall,返回带 MultiIndex 的 DataFrame,最后一层叫 match 表示第几次匹配:
import pandas as pd
s = pd.Series(["a1a2", "b1", "c1"], dtype="str")
print(s.str.extractall(r"(?P<letter>[a-z])(?P<digit>\d)"))
# letter digit
# match
# 0 0 a 1
# 1 a 2
# 1 0 b 1
# 2 0 c 1
replace:按模式替换
str.replace 默认就开正则(regex=True)。想按字面替换,明确写 regex=False 更稳:
import pandas as pd
s = pd.Series(["价格:$100", "价格:$200", "价格:0"], dtype="str")
print(s.str.replace(r"\$\d+", "***", regex=True))
# 0 价格:***
# 1 价格:***
# 2 价格:0
# regex=False:点号按普通字符处理,不会匹配任意字符
t = pd.Series(["a.b", "b.c", "c"], dtype="str")
print(t.str.replace(".", "-", regex=False))
# 0 a-b
# 1 b-c
# 2 c
替换串里可以用 \1、\2 引用捕获组;也可以传一个函数,收到匹配对象后返回替换文本,适合”把匹配到的内容加工后再替换”的场景。
Tip正则写多了容易眼花。先在纯 Python 里用
re.findall调试好模式,再搬进 .str 方法。.匹配任意字符、$是行尾锚点,这些元字符要匹配字面含义必须转义——用regex=False能避开一大半转义问题。
常用正则小抄
不用背,用到查表:
| 模式 | 含义 | 例子 |
|---|---|---|
\d | 数字 | \d{4} 匹配 4 位数字 |
\w | 字母、数字、下划线 | \w+ 匹配一个”单词” |
\s | 空白(空格、Tab、换行) | \s+ 匹配连续空白 |
. | 任意字符(换行除外) | a.c 能匹配 “abc” |
^ / $ | 开头 / 结尾 | ^订单 匹配以”订单”开头 |
[ab] | 字符集合,a 或 b | [0-9] 匹配一位数字 |
{n,m} | 重复 n 到 m 次 | \d{2,4} 匹配 2 到 4 位数字 |
| `(x | y)` | 分支:x 或 y |
StringDtype:3.0 的新默认字符串类型
pandas 3.0 之前,字符串列默认是 NumPy 的 object 类型——它其实是个”杂物间”,什么 Python 对象都能往里塞。3.0 起字符串有了自己的类型:StringDtype,别名 "str"。你什么都不用做,只要创建字符串 Series,pandas 就自动推断成它:
import pandas as pd
s = pd.Series(["a", "b", None])
print(s.dtype)
# str
# 缺失值统一用 NaN,和 float 列一致
print(s)
# 0 a
# 1 b
# 2 NaN
# dtype: str
StringDtype 的几个关键特性:
- 只存字符串:往里面塞数字直接报错,object 时代”一列混着字符串和数字”的乱象没有了。
- 缺失值统一为 NaN:之前 object 列里 None、NaN 混着来,现在全部归一成 NaN。
- 底层优先用 PyArrow:装了 pyarrow 包时,字符串由 Arrow 存储,内存占用和运算速度都优于 object;没装则退回 NumPy object 存储,行为不变。
astype("str")保留缺失:旧版astype(str)会把 NaN 变成字符串 “nan”,3.0 起缺失值保持缺失。想”全部转成字符串”,用s.map(str)。
和 object 对比,一句话总结:
| 对比项 | object(旧) | StringDtype(3.0 默认) |
|---|---|---|
| 能存什么 | 任意 Python 对象 | 只能字符串 + 缺失值 |
| 缺失值 | None、NaN 混杂 | 统一 NaN |
| 内存效率 | 低 | 高(PyArrow 存储时) |
| dtype 显示 | object | str |
Warning两个容易踩的坑:一是用
s.dtype == "object"判断字符串列会失效,改用s.dtype == "str"或pd.api.types.is_string_dtype(s.dtype);二是 3.0 里往字符串列赋值非字符串值会抛 TypeError,确实需要混存时先s.astype("object")。
还有两个细节。取底层数据时,s.values 不再返回 NumPy 数组而是 ExtensionArray,想要 NumPy 数组用 s.to_numpy();按类型筛列时,df.select_dtypes(include=["str"]) 能精准选出字符串列。还在用 pandas 2.x 的读者想提前体验新类型,可以设 pd.options.future.infer_string = True。
还有个容易混的别名 "string":它同样代表 StringDtype,但缺失值用 pd.NA 而不是 NaN。日常用默认的 "str" 就够了。
小结
正则让 .str 从”会切菜”升级到”能按菜谱找食材”:contains 判断、extract 提取、replace 替换,三件套覆盖绝大多数文本清洗场景。StringDtype 则是 3.0 的地基变化——字符串不再是 object 的”租客”,有了自己的家,更省内存也更安全。下一节讲分类数据 Categorical,它和字符串类型配合,是处理”取值有限”的文本列的两大法宝。