re.sub回调函数必须接收单个re.Match对象参数,返回字符串以替换匹配内容;若返回None则静默替换为空串。

re.sub 的回调函数参数签名是什么
re.sub 接收的回调函数必须是单参数函数,该参数是 re.Match 对象,不是字符串。常见错误是误以为能直接拿到匹配到的字符串,结果在回调里写 match.group() 时没加括号报 TypeError: 'str' object is not callable。
回调函数返回值决定替换内容:返回字符串则替换为该字符串;返回 None(比如忘了 return)会导致替换成空字符串,且不报错,极难排查。
- 必须接收一个
match参数 - 应显式调用
match.group()或match.group(0)获取完整匹配文本 - 若需捕获组内容,用
match.group(1)、match.group('name') - 返回值不能是
None,否则静默变空串
动态替换常见场景:大小写翻转与编号插入
大小写翻转不是简单用 str.upper(),而是要根据原始匹配内容判断。例如把所有单词首字母大写、其余小写,但保留原缩写习惯(如 “iOS” 不应变成 “Ios”),这时就得在回调里做逻辑分支。
编号插入更典型:给每段代码块加行号,或把日志中的时间戳替换成序号。关键点是状态需要跨多次回调保持,不能在函数内定义计数器——因为每次回调都是独立调用。
立即学习“Python免费学习笔记(深入)”;
- 用闭包或类封装状态(推荐类,更清晰)
- 避免用全局变量,模块级变量在多线程或重复调用时会出错
- 示例:给连续出现的
def行加序号class Counter: def __init__(self): self.n = 0 def __call__(self, match): self.n += 1 return f"[{self.n}] {match.group(0)}" </li></ul><p>re.sub(r'^def\s+\w+', Counter(), source, flags=re.MULTILINE)回调里访问正则捕获组和原始字符串的细节
match.group(0)是整个匹配结果,match.group(1)是第一个捕获组,命名组用match.group('name')。但注意:如果正则中用了非捕获组(?:...),它不会出现在group()索引里,别数错位置。另一个坑是原始字符串未被修改前的上下文无法直接获取——
match对象不提供前文后文。若需前后文判断(比如只替换“function”但不在注释里),得改用re.finditer手动遍历 +span()定位,再切片检查。- 捕获组索引从 1 开始,
group(0)固定为全匹配 - 命名组名必须是合法标识符,且正则中需写成
(?P<name>...) -
match.string是原始输入字符串,可用来做上下文分析(但性能略低) - 若正则含
re.DOTALL,.匹配换行符,match.group()可能含换行,注意替换后格式错乱
性能陷阱:回调函数别做重操作
每次匹配都会调用一次回调,若回调里打开文件、查数据库、解析 JSON,整体性能会断崖式下降。曾见有人在回调里调用
requests.get请求外部 API 替换 URL,结果 100 个匹配触发 100 次 HTTP 请求。真正需要动态计算的逻辑应尽量轻量:字符串拼接、条件判断、查预加载字典。复杂处理建议先用
re.finditer提取所有匹配位置和内容,批量处理完再一次性re.sub或手动拼接。- 回调执行频率 = 匹配次数,不是“执行一次”
- 避免在回调中做 I/O、网络、锁竞争操作
- 若必须查表,确保字典已构建好,别在回调里重复初始化
实际用多了就会发现:最难的往往不是语法,而是想清楚“哪些逻辑真需要动态做”,而不是把所有转换都塞进回调里。
- 捕获组索引从 1 开始,


















