CPython/re · SRE 匹配引擎

通过回溯调整正则表达式的匹配范围

CPython 的正则引擎先尝试较长的重复匹配,后续模式失败时恢复状态、缩短重复范围,并跳过不可能成功的位置。

v3.13.0检索与计算核验 2026-10-12
相关概念

工程问题#

一个重复项先匹配了多少字符,会影响后面的模式是否还能匹配。例如 a*(ab) 匹配 aaab 时,a* 先取三个 a,之后必须退回一个 a,才能让捕获组匹配 ab。引擎需要重试候选位置,并保持捕获组状态正确。

实际实现#

Pattern.fullmatch 把编译后的模式交给 SRE。对单字符宽度的 REPEAT_ONE,先统计可匹配的最大重复次数,再尝试剩余模式。失败后恢复捕获标记,向前移动指针并减少重复次数;若后续模式以确定的字符开头,还会直接跳过字符不符的位置。

处理流程#

  1. 初始化目标字符串和匹配状态,要求模式覆盖整个指定范围
  2. 对单字符重复项先取允许范围内的最大重复次数
  3. 尝试剩余模式;失败则恢复捕获状态,减少一次重复后再试
  4. 若后续首字符已知,先跳过不可能的起点;成功则返回匹配,否则在候选耗尽后失败

源码片段#

固定版本 · 原文摘录
Modules/_sre/sre.cv3.13.0 · L737–L756
查看原文件
737    if (!state_init(&state, self, string, pos, endpos))738        return NULL;739740    INIT_TRACE(&state);741    state.ptr = state.start;742743    TRACE(("|%p|%p|FULLMATCH\n", PatternObject_GetCode(self), state.ptr));744745    state.match_all = 1;746    status = sre_match(&state, PatternObject_GetCode(self));747748    TRACE(("|%p|%p|END\n", PatternObject_GetCode(self), state.ptr));749    if (PyErr_Occurred()) {750        state_fini(&state);751        return NULL;752    }753754    match = pattern_new_match(module_state, self, &state, status);755    state_fini(&state);756    return match;
_sre_SRE_Pattern_fullmatch_impl

fullmatch 初始化状态,设置覆盖整个范围的标记,调用匹配器,再从最终状态构造匹配结果。

Modules/_sre/sre_lib.hv3.13.0 · L899–L913
查看原文件
899            state->ptr = ptr;900901            ret = SRE(count)(state, pattern+3, pattern[2]);902            RETURN_ON_ERROR(ret);903            DATA_LOOKUP_AT(SRE(match_context), ctx, ctx_pos);904            ctx->count = ret;905            ptr += ctx->count;906907            /* when we arrive here, count contains the number of908               matches, and ptr points to the tail of the target909               string.  check if the rest of the pattern matches,910               and backtrack if not. */911912            if (ctx->count < (Py_ssize_t) pattern[1])913                RETURN_FAILURE;
SRE(match) / SRE_OP_REPEAT_ONE

先按重复上限统计能匹配的字符数,并把指针移到重复段末尾;随后还必须验证模式的剩余部分。

Modules/_sre/sre_lib.hv3.13.0 · L958–L978
查看原文件
958            } else {959                /* general case */960                while (ctx->count >= (Py_ssize_t) pattern[1]) {961                    state->ptr = ptr;962                    DO_JUMP(JUMP_REPEAT_ONE_2, jump_repeat_one_2,963                            pattern+pattern[0]);964                    if (ret) {965                        if (state->repeat)966                            MARK_POP_DISCARD(ctx->lastmark);967                        RETURN_ON_ERROR(ret);968                        RETURN_SUCCESS;969                    }970                    if (state->repeat)971                        MARK_POP_KEEP(ctx->lastmark);972                    LASTMARK_RESTORE();973974                    ptr--;975                    ctx->count--;976                }977                if (state->repeat)978                    MARK_POP_DISCARD(ctx->lastmark);
SRE_OP_REPEAT_ONE / jump_repeat_one_2

DO_JUMP 尝试剩余模式。失败后恢复捕获标记,减少一个字符的重复范围并重试;这体现了回溯的状态恢复和候选推进。

Modules/_sre/sre_lib.hv3.13.0 · L928–L942
查看原文件
928            if (pattern[pattern[0]] == SRE_OP_LITERAL) {929                /* tail starts with a literal. skip positions where930                   the rest of the pattern cannot possibly match */931                ctx->u.chr = pattern[pattern[0]+1];932                for (;;) {933                    while (ctx->count >= (Py_ssize_t) pattern[1] &&934                           (ptr >= end || *ptr != ctx->u.chr)) {935                        ptr--;936                        ctx->count--;937                    }938                    if (ctx->count < (Py_ssize_t) pattern[1])939                        break;940                    state->ptr = ptr;941                    DO_JUMP(JUMP_REPEAT_ONE_1, jump_repeat_one_1,942                            pattern+pattern[0]);
SRE_OP_REPEAT_ONE / literal tail pruning

如果剩余模式以字面字符开头,先跳过目标字符不符的位置,再调用后续匹配。这是在完整尝试前排除不可能候选的剪枝。

摘录展示 fullmatch 入口、重复计数、失败后的状态恢复,以及尾部首字符剪枝。编译器、分组栈和其他重复指令位于同版源码中。

收益与取舍#

工程分析

带来的收益

  • 不用枚举并保存所有切分方案,就能按模式语义逐个尝试候选匹配范围
  • 恢复捕获状态避免失败分支污染成功结果,首字符检查可排除部分无效候选

付出的代价

  • 匹配成本取决于模式的嵌套结构、候选范围和失败位置;局部剪枝不能保证整个引擎线性运行
  • 这个快速分支只处理单字符宽度的重复项,更复杂的重复表达式使用其他指令与状态处理

可比较的选择

占有量词与原子组可以阻止指定范围内的回退,但会改变可接受的匹配。例如 a*+(ab) 不会退还已经匹配的 a,因而无法匹配 aaab。

改写模式以消除重叠候选明确分隔符或字符范围通常能减少重试,但必须核对是否保留原模式的匹配与捕获语义。

版本与适用范围#

固定为 CPython v3.13.0 的 SRE 单字符重复分支,不代表所有正则操作都走同一路径。SRE 使用显式匹配上下文,不能将算法上的回溯等同于 C 函数递归。正则表达式中的 greedy 表示优先尝试较长匹配,不等同于求解最优化问题的贪心策略。

参考来源#

源码、官方文档与原始论文
01
fullmatch 的实际入口

Modules/_sre/sre.c · _sre_SRE_Pattern_fullmatch_impl

源码v3.13.0
02
单字符重复指令

Modules/_sre/sre_lib.h · SRE(match) / SRE_OP_REPEAT_ONE

源码v3.13.0
03
回溯与捕获状态恢复

Modules/_sre/sre_lib.h · SRE_OP_REPEAT_ONE / jump_repeat_one_2

源码v3.13.0
04
字面字符剪枝

Modules/_sre/sre_lib.h · SRE_OP_REPEAT_ONE / literal tail pruning

源码v3.13.0
05
正则表达式编译器的重复指令选择

Lib/re/_compiler.py · _compile

源码v3.13.0
19 个匹配案例