re.sub 的 callback 必须接收 re.Match 对象并返回字符串,否则报错;需检查分组是否存在、善用 groupdict 和上下文闭包,避免回调内编译正则或 I/O。
回调函数不是随便写个函数就能塞进 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() 获取位置信息当正则含多个命名组((?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./link/28b1723af782c5ebb1f6522d19c6df31 in match.groupdict().items(): if https://www./link/28b1723af782c5ebb1f6522d19c6df31 is not None and name in FORMAT_MAP: parts.append(FORMAT_MAPname) return "".join(parts)
text = "会议时间:2025-04-15" result = re.sub(r"(?P
\d{4})-(?P \d{2})-(?P \d{2})", format_date, text) → "会议时间:【2025年】04月15日"
re.sub 回调默认隔离,但实际常需依赖上下文——比如只替换位于字母后的数字,或按顺序给匹配项编号。这时不能只靠 match 对象,得引入外部变量或闭包。
counter = iter(range(1, 100)),回调里 next(counter)
m.string[m.start()-1:m.start()] 拿前一个字符(注意越界,先判 m.start() > 0)functools.pa
rtial 绑定上下文对象每次匹配都会调用回调,若里面执行 re.compile() 或遍历上万条记录的 dict,性能断崖式下跌。实测 10 万次回调中做一次 re.compile,比提前编译好慢 30 倍以上。
re.finditer 收集所有 Match,再批量处理,最后用 str.replace 或拼接真正复杂的替换逻辑,往往不是正则不够强,而是过早把业务规则塞进回调里——先拆出匹配,再单独写转换,更易测、易调、易改。