re.sub 的 callback 必须接收 re.Match 对象并返回字符串,否则报错;需检查分组是否存在、善用 groupdict 和上下文闭包,避免回调内编译正则或 I/O。

re.sub 的 callback 参数到底传什么
回调函数不是随便写个函数就能塞进 re.sub 的——它必须接收一个 re.Match 对象作为唯一参数,返回字符串。传错类型(比如返回 None 或数字)会导致替换结果变成字面的 None 或触发 TypeError。
常见错误:写成 lambda m: m.group(1).upper() 却没检查 m.group(1) 是否存在,一旦分组未匹配,.group(1) 抛 IndexError,整个 sub 失败。
- 务必用
m.group(1)前加if m.group(1)判断,或改用m.groupdict().get("name") - 回调里可安全调用
m.start()、m.end()、m.span()获取位置信息 - 不要在回调里修改原始字符串,它只读;所有替换逻辑靠返回值驱动
用 groupdict 实现命名捕获 + 动态映射
当正则含多个命名组((?P),硬编码 m.group(1) 易错且难维护。用 m.groupdict() 转成字典,再结合预定义映射表,能清晰分离“识别”和“转换”逻辑。
import reFORMAT_MAP = { "year": lambda v: f"【{v}年】", "month": lambda v: f"{v}月", "day": lambda v: f"{v}日" }
def format_date(match): parts = [] for name, https://www.php.cn/link/28b1723af782c5ebb1f6522d19c6df31 in match.groupdict().items(): if https://www.php.cn/link/28b1723af782c5ebb1f6522d19c6df31 is not None and name in FORMAT_MAP: parts.append(FORMAT_MAPname) return "".join(parts)
text = "会议时间:2023-04-15" result = re.sub(r"(?P
\d{4})-(?P \d{2})-(?P \d{2})", format_date, text) → "会议时间:【2023年】04月15日"
替换时访问上下文:前后字符与全局状态
re.sub 回调默认隔离,但实际常需依赖上下文——比如只替换位于字母后的数字,或按顺序给匹配项编号。这时不能只靠 match 对象,得引入外部变量或闭包。
立即学习“Python免费学习笔记(深入)”;
- 用闭包维护计数器:
counter = iter(range(1, 100)),回调里next(counter) - 用
m.string[m.start()-1:m.start()]拿前一个字符(注意越界,先判m.start() > 0) - 避免用全局变量,多线程下不安全;推荐
functools.partial绑定上下文对象
性能陷阱:别在回调里反复编译正则或查大字典
每次匹配都会调用回调,若里面执行 re.compile() 或遍历上万条记录的 dict,性能断崖式下跌。实测 10 万次回调中做一次 re.compile,比提前编译好慢 30 倍以上。
- 所有正则、映射表、格式化函数,一律在回调定义前准备好
- 避免在回调里做 I/O(如读文件、发 HTTP 请求)
- 若需复杂逻辑,先用
re.finditer收集所有Match,再批量处理,最后用str.replace或拼接
真正复杂的替换逻辑,往往不是正则不够强,而是过早把业务规则塞进回调里——先拆出匹配,再单独写转换,更易测、易调、易改。










