广告

Python 替换文本完全指南:sub 与 subn 的区别与实战技巧

一、基础概念与使用场景

sub 与 subn 的核心定义

在 Python 的 re 模块中,sub() 用于按照正则表达式替换文本,返回替换后的新字符串;subn() 在功能类似的基础上,额外返回替换次数,形式为 (, count)。

这两者都依赖正则模式、替换文本和原始字符串,区别在于一个返回结果结构,另一个会给出替换发生的次数。这使得在文本处理流水线中,你可以按需选择,既获得结果也获得统计信息。

在本文中,我们以“Python 替换文本”为主线,围绕 sub 与 subn 的区别、参数、技巧以及实际案例展开讲解,帮助你在真实场景中快速落地。

import re
text = "abc 123 def 456"
# 使用 sub 将数字替换成 '#'
result = re.sub(r"\d+", "#", text)
print(result)  # abc # def #
import re
text = "abc 123 def 456"
new, n = re.subn(r"(\d+)", r"#", text)
print(new)  # abc # def #
print(n)    # 2

常见用途与场景

sub 的直接返回结果适合后续拼接、输出或写回文件等场景;subn 则在需要统计替换次数、评估覆盖率时更具价值。

常见的文本处理场景包括日志清洗、格式标准化、敏感信息脱敏与数据变换等。通过结合捕获组、回调函数与 count 参数,可以实现更加灵活的替换策略。

import re
sample = "Name: Alice, Name: Bob"
# 将 Name: 后的名字规范化为 Name-Alice、Name-Bob 的形式
result = re.sub(r"Name:\s*(\w+)", r"Name-\1", sample)
print(result)  # Name- Alice, Name- Bob

二、sub 与 subn 的返回值与行为差异

返回值的核心差异

sub() 直接返回一个新字符串;subn() 返回一个元组 (new_string, count)。这一点在处理大文本时尤为重要,能让你在一次替换后获得统计信息。

例如:对同一个文本应用相同的模式,两者在语义上等价,但返回的数据结构不同,从而影响后续的逻辑处理。

import re
s = "aa bb aa"
print(re.sub(r"aa", "A", s))   # A bb A
print(re.subn(r"aa", "A", s))  # ('A bb A', 2)

计数信息的使用场景

通过获取 count,你可以判断替换是否命中,以及替换的密集程度,这对日志解析、统计信息输出、以及分步处理逻辑都是有益的。

当你在替换过程中需要动态条件时,也可以结合回调函数来实现更复杂的替换策略。

import re
def repl(m):# 将数字替换为其平方的字符串表示n = int(m.group())return str(n*n)
text = "2 3 4"
print(re.sub(r"\d+", repl, text))  # 4 9 16

三、实战技巧:高效替换的常用模式

在 repl 中使用回调函数实现复杂逻辑

除了简单的字符串替换,re.subre.subn 支持将 repl 设为一个函数,该函数接收匹配对象并返回替换文本。这使得替换过程可以根据上下文动态决定替换内容,非常适合实现分段替换、条件替换等场景。

import re
text = "user: alice; user: bob; user: carol"
def repl(m):u = m.group(1)return f"<{u}>"
out = re.sub(r"user:\s*(\w+)", repl, text)
print(out)  #   

很多情况下用捕获组引用简化替换文本

在替换字符串中,通过捕获组引用可以直接复用匹配的部分,例如把日期从 YYYY-MM-DD 统一改成 YYYY/MM/DD 的形式。注意要使用原始字符串并正确引用组:r'\g<1>/\g<2>/\g<3>',或简写为 r'\1/\2/\3'

Python 替换文本完全指南:sub 与 subn 的区别与实战技巧

import re
text = "2024-03-15"
out = re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\1/\2/\3", text)
print(out)  # 2024/03/15

限制替换次数以提升性能

如果你只关心前 n 次替换,count 参数可以限制替换数量,从而避免对整个字符串进行大量不必要的处理,尤其在大文本中极大提升性能。

import re
text = "foo1 bar2 baz3 qux4"
# 只替换前两处数字
out = re.sub(r"\d+", "NUMBER", text, count=2)
print(out)  # NUMBER bar2 baz3 qux4

四、进阶案例与性能提示

预编译模式提升重复替换的效率

对于需要多次执行相同模式替换的场景,使用 re.compile 先将模式编译成对象,再反复调用 subsubn,可以显著降低编译开销。

import re
pattern = re.compile(r"([A-Z]+)-(\d+)")
text = "ABC-123 DEF-456"
print(pattern.sub(r"\1:\2", text))  # ABC:123 DEF:456
print(pattern.subn(r"\1:\2", text)) # ('ABC:123 DEF:456', 2)

避免在循环中每次都重新创建模式

把模式对象在循环外部创建好,并在循环中重复使用,可以减少正则的编译成本,尤其是在处理大批量文本时。

import re
text_list = ["AA-12", "BB-34", "CC-56"]
pattern = re.compile(r"([A-Z]+)-(\d+)")
for t in text_list:print(pattern.sub(r"\1:\2", t))

五、实战案例汇总

案例一:统一日期表示法

目标:将日期格式统一成 YYYY/MM/DD。subn 提供替换后文本和命中次数,便于统计覆盖率。

import re
texts = ["2024-03-15","2025-12-01"
]
pattern = re.compile(r"(\d{4})-(\d{2})-(\d{2})")
out = [pattern.sub(r"\1/\2/\3", t) for t in texts]
print(out)  # ['2024/03/15', '2025/12/01']

案例二:隐藏电子邮件域名并演示回调替换

场景需求:对日志中的邮箱进行脱敏处理,保留用户名但把域名部分替换为星号。结合回调函数实现灵活控制。

import re
logs = "alice@example.com, bob@sample.org"
def mask_domain(m):user = m.group(1)domain = m.group(2)return f"{user}@***.***"
pattern = re.compile(r"([a-zA-Z0-9_.+-]+)@([a-zA-Z0-9.-]+)")
print(pattern.sub(mask_domain, logs))

广告

后端开发标签