使用Python Lark无树解析时出现参数缺失错误,原因是什么?
Lark无树LALR解析Transformer报错原因分析
问题背景
使用Lark解析日志中的等式,切换到LALR算法提升性能后,尝试启用无树(tree-less)解析进一步优化,出现以下报错:
TypeError: ToDict.identifier() missing 1 required positional argument: 'ident'
当OPT=False(先解析生成语法树再用Transformer转换)时正常返回{'a': 3.14, 'b': [1.41, 1.732]},但OPT=True(直接将Transformer绑定到无树LALR解析器)时触发上述错误。
错误含义解析
这个错误的核心是:无树LALR模式下,Transformer方法的参数传递逻辑和常规模式完全不同。
- 常规模式(生成语法树后转换)中,所有Transformer方法接收的都是子节点组成的列表,比如
identifier方法的ident参数是包含单个Token的列表,所以你可以用(ident,) = ident解包。 - 但在无树模式下,对于只包含单个终端符号的规则(比如
identifier: CNAME、num: SIGNED_NUMBER),Lark会直接传递终端符号对应的Token本身,而不是列表。此时你的方法定义仍然按列表接收参数,就会出现参数不匹配的报错——Lark传递了1个值(Token),但方法期望接收一个列表作为ident参数,导致解释器认为缺少了位置参数。
解决方案
方法1:兼容两种模式的参数处理
修改Transformer中对应终端符号的方法,判断输入是否为列表,兼容两种模式:
class ToDict(Transformer): def equations(self, eqs): return {lhs: rhs for eq in eqs for lhs, rhs in eq.items()} def equation(self, eq): (ident, _rhs) = eq return {ident: _rhs} def rhs(self, num_vec): (num_vec,) = num_vec return num_vec def identifier(self, ident): # 兼容列表(常规模式)和单个Token(无树模式) ident_val = ident[0] if isinstance(ident, list) else ident return str(ident_val) def num(self, n): n_val = n[0] if isinstance(n, list) else n return float(n_val) def vector(self, vec): return list(vec)
方法2:使用@v_args装饰器统一参数传递
Lark提供的@v_args装饰器可以自动处理两种模式的参数差异,让方法直接接收子节点作为单独参数:
from lark import Lark, Transformer, v_args OPT = True def parser() -> Lark: grammar = r""" equations: [equation ("," equation)*] equation: identifier "=" rhs identifier: CNAME rhs: num | vector vector: "[" [num ("," num)*] "]" num: SIGNED_NUMBER %import common.CNAME %import common.SIGNED_NUMBER %import common.WS %ignore WS """ if OPT: eq_parser = Lark(grammar, start="equations", parser="lalr", transformer=ToDict()) else: eq_parser = Lark(grammar, start="equations", parser="lalr") return eq_parser class ToDict(Transformer): @v_args(inline=True) def equations(self, *eqs): return {lhs: rhs for eq in eqs for lhs, rhs in eq.items()} @v_args(inline=True) def equation(self, ident, _rhs): return {ident: _rhs} @v_args(inline=True) def rhs(self, num_vec): return num_vec @v_args(inline=True) def identifier(self, ident): return str(ident) @v_args(inline=True) def num(self, n): return float(n) def vector(self, vec): return list(vec) if __name__ == "__main__": line = "a=3.14, b=[1.41, 1.732]" prsr = parser() if OPT: parsed = prsr.parse(line) else: parsed = ToDict().transform(prsr.parse(line)) print(parsed)
注意:使用装饰器时,初始化Transformer需要加括号transformer=ToDict(),而不是transformer=ToDict。
内容的提问来源于stack exchange,提问作者user3389669
相关产品推荐
相关产品推荐

