应优先使用 pandas.Series.str.extract 和 str.replace 替代循环+re.search/re.sub,因其底层向量化实现快5–10倍;需用命名捕获组、预编译正则、避免expand=True和动态flags,并妥善处理null值。

用 pandas.Series.str.extract 替代循环 + re.search
对百万级字符串列逐行调用 re.search 是性能杀手,Python 解释器开销远超正则本身。直接用 Series.str.extract 走底层 vectorized 实现,速度通常快 5–10 倍。
- 必须确保正则含命名捕获组(如
(?P<year>\d{4})</year>),否则返回 DataFrame 列名会是数字索引,后续难维护 - 若只需单个匹配结果,别用
extractall—— 它返回长格式 MultiIndex,内存暴涨且多数场景不需要 -
extract遇到无匹配时填NaN,若需默认值(如空字符串),得接.fillna(''),不能靠default参数(该参数仅存在于str.get等少数方法)
替换操作优先选 str.replace 而非 apply + re.sub
Series.str.replace 底层复用相同的编译缓存机制,且避免 Python 层循环;而 apply(lambda x: re.sub(...)) 每次都触发函数调用和正则重编译(除非显式预编译)。
- 简单字面量替换(如把所有
'abc'换成'xyz')直接传字符串,比传re.compile对象还快 —— pandas 内部做了优化 - 复杂正则替换务必预编译:用
pattern = re.compile(r'\s+'),再传入str.replace(pattern, ' '),否则每次调用都重新 compile - 不要在
replace中用regex=False去“规避正则”,它只禁用元字符,但底层仍走正则引擎路径,纯属误导;真要字面替换就传字符串
内存敏感时禁用 expand=True 和 flags 动态传参
str.extract(..., expand=True) 默认返回 DataFrame,哪怕只有一组也多一层封装;expand=False 返回 Series,省内存、少拷贝。同理,flags 参数若每次调用都传(如 flags=re.I),pandas 无法复用编译结果。
- 把
flags写进正则字符串里更稳:用r'(?i)hello'代替r'hello'+flags=re.I - 提取单列时强制
expand=False,返回的是Series而非单列DataFrame,链式操作(如.astype(int))更直接 - 若原始列含 null(
pd.NA或None),str.extract默认跳过,但str.replace会报错,需提前用.fillna('')或加regex=False(仅限字面替换)兜底
超大规模(千万级以上)考虑 dask 或 modin 替代原生 pandas
当单机内存扛不住、且正则逻辑不依赖跨行上下文(如无需 lookbehind/lookahead 全局匹配),可切分后并行处理。但注意:dask 的 str 方法支持度有限,modin 更接近 pandas 接口但仍有差异。
立即学习“Python免费学习笔记(深入)”;
-
modin.pandas可无修改运行大部分str.extract/replace代码,但flags参数可能被忽略,务必实测 - 自己分块时别用
df.iloc[i:j]切片再apply—— 这仍是串行;改用df.partitions[n].str.extract(...)(dask)或df.map_partitions(lambda x: x.str.extract(...)) - 正则本身复杂度影响更大:一个
.*开头的贪婪模式,在千万行上可能比十个简单模式还慢,先 profile 单条正则耗时
真正卡顿往往不在“怎么写正则”,而在没意识到 str.extract 默认返回 DataFrame、或忘了 null 值会让 replace 报 AttributeError: 'float' object has no attribute 'replace' —— 这类隐性类型断裂,比语法错误更难定位。


















