一、基础概念与使用场景
sub 与 subn 的核心定义
在 Python 的 re 模块中,sub() 用于按照正则表达式替换文本,返回替换后的新字符串;subn() 在功能类似的基础上,额外返回替换次数,形式为 (
这两者都依赖正则模式、替换文本和原始字符串,区别在于一个返回结果结构,另一个会给出替换发生的次数。这使得在文本处理流水线中,你可以按需选择,既获得结果也获得统计信息。
在本文中,我们以“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.sub 和 re.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'。

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 先将模式编译成对象,再反复调用 sub 或 subn,可以显著降低编译开销。
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))


