用 re.finditer 配合命名捕获组提取带类型的 token,按优先级排列分支(关键字在标识符前),跳过空白与注释;用 namedtuple 封装 token 保证字段固定、属性访问清晰、不可变;逐字符维护 lineno/colno 确保定位精准。

怎么用 re.findall 拆出带类型的 token?
直接用 re.findall 做词法分析,关键不是写一个大正则,而是让每个 token 能同时捕获「文本内容」和「类型标签」。推荐用命名捕获组 + re.finditer,因为 findall 对多选分支返回元组时容易错位(比如 (?P<number>\d+)|(?P<id>[a-zA-Z_]\w*)</id></number> 会返回 ('123', '') 或 ('', 'x'),处理起来反直觉。
实操建议:
- 用
re.finditer遍历所有匹配,检查哪个 group 名不为None - 每个 pattern 分支对应一种 token 类型,避免重叠(比如
==必须比=写在前面) - 跳过空白和注释:用
if token_type in ('WS', 'COMMENT'):继续下一轮
为什么用 NamedTuple 而不是 dict 或普通 tuple?
NamedTuple 在这里不是为了“显得高级”,而是解决三个实际问题:一是 token 必须有固定字段(type、value、lineno、colno),二是要支持属性访问(t.type 比 t[0] 清晰且防错),三是不可变性天然防止误改 value 导致后续解析错乱。
注意点:
- 别用
typing.NamedTuple(Python 3.6+),它不支持默认值;用collections.namedtuple或 Python 3.7+ 的typing.NamedTuple子类(带_field_defaults) - 如果需要 lineno/colno,别依赖
match.start()算列号——换行符长度不一致(\r\nvs\n),应逐行扫描并维护当前行号和列偏移
如何处理关键字与标识符的优先级冲突?
关键字(如 if、while)本质是特殊标识符,但必须在 ID 规则之前匹配,否则全被当成普通变量名。常见错误是把 ID 放最前,结果 if 变成 ID 类型,后续语法分析直接失败。
正确做法:
- 把所有关键字正则写成独立分支,按长度从长到短排列(比如
elif在if前) - 或统一用一个分支匹配
ID,然后在构造 token 时查表判断:token_type = KEYWORDS.get(token_value, 'ID') - 查表方式更易维护,但要注意大小写敏感性(Python 关键字全小写,但用户 ID 可能大写)
为什么不能省略行号和列号的计算逻辑?
报错信息里只说 SyntaxError: invalid syntax 是没用的。真实调试依赖精确位置,而位置不是靠 match.start() 除以行宽能算准的——源码含制表符、混合换行符、Unicode 全角空格都会让列号偏移。
安全做法:
- 逐字符扫描时维护
lineno和colno,遇到\n就重置colno = 0,遇到\t按 4 或 8 位补(按编辑器设置,但至少保持内部一致) - 对每个
match,用match.start()回溯找上一个换行符位置,再减去得到列号,比边扫边计数更准(尤其处理多行字符串字面量时) - 别在 token 构造完再统一算位置——中间跳过注释/空行时,位置状态已丢失
最麻烦的其实是字符串字面量里的转义和换行,一旦支持三引号或续行符,位置计算就得退回到逐字符状态。简单词法分析器可以先禁掉这些特性,明确标注“不支持多行字符串”,比算错位置更可接受。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











