Skip to main content

32.1. parser —访问Python解析树


parser 模块为Python的内部解析器和字节码编译器提供了一个接口。此接口的主要目的是允许Python代码编辑Python表达式的解析树并从中创建可执行代码。这比尝试将任意Python代码片段解析和修改为字符串更好,因为解析是以与形成应用程序的代码相同的方式执行的。它也更快。

注解

从Python 2.5开始,使用 ast 模块在抽象语法树(AST)生成和编译阶段中切入更为方便。

有一些要注意的事情,这个模块对于使用创建的数据结构很重要。这不是一个关于编辑Python代码的解析树的教程,但是提供了使用 parser 模块的一些示例。

最重要的是,需要对内部解析器处理的Python语法有一个很好的理解。有关语言语法的完整信息,请参阅 Python语言参考。解析器本身是从标准Python分发版中 Grammar/Grammar 文件中定义的语法规范创建的。存储在由此模块创建的ST对象中的解析树是由 expr()suite() 函数创建时内部解析器的实际输出,如下所述。由 sequence2st() 创建的ST对象忠实地模拟这些结构。请注意,被认为“正确”的序列的值将随着Python的一个版本而变化,因为该语言的形式语法被修改。但是,将代码从一个Python版本传输到另一个作为源文本将始终允许在目标版本中创建正确的解析树,唯一的限制是迁移到旧版本的解释器将不支持更多的最近的语言结构。解析树通常不是从一个版本到另一个版本是兼容的,而源代码总是向前兼容的。

st2list()st2tuple() 返回的序列的每个元素都有一个简单的形式。表示语法中的非终端元素的序列总是具有大于1的长度。第一个元素是标识语法中的生产的整数。这些整数在C头文件 Include/graminit.h 和Python模块 symbol 中被赋予符号名称。序列的每个附加元素表示在输入字符串中识别的生产的组件:这些总是与父组件具有相同形式的序列。应当注意的这种结构的一个重要方面是用于标识父节点类型的关键字,例如 if_stmt 中的关键字 if,被包括在节点树中而没有任何特殊处理。例如,if 关键字由元组 (1, 'if') 表示,其中 1 是与所有 NAME 令牌相关联的数值,包括由用户定义的变量和函数名称。在请求行号信息时返回的备选形式中,相同的令牌可以表示为 (1, 'if', 12),其中 12 表示找到终端符号的行号。

终端元素以大致相同的方式表示,但没有任何子元素,并且添加了被识别的源文本。上面的 if 关键字的示例是有代表性的。各种类型的终端符号在C头文件 Include/token.h 和Python模块 token 中定义。

ST对象不需要支持该模块的功能,但是被提供用于三个目的:允许应用分摊处理复杂解析树的成本,以提供解析树表示,其与Python相比节省存储器空间列表或元组表示,并且易于在C中创建操纵解析树的附加模块。可以在Python中创建一个简单的“包装器”类来隐藏ST对象的使用。

parser 模块定义了几个不同目的的函数。最重要的目的是创建ST对象并将ST对象转换为其他表示形式,如解析树和编译代码对象,但也有用于查询由ST对象表示的解析树类型的函数。

参见

模块 symbol

有用的常数表示解析树的内部节点。

模块 token

有用的常数表示解析树的叶节点和用于测试节点值的函数。

32.1.1. 创建ST对象

ST对象可以从源代码或从解析树创建。当从源创建ST对象时,使用不同的函数来创建 'eval''exec' 表单。

parser.expr(source)

expr() 函数解析参数 source,就像它是 compile(source, 'file.py', 'eval') 的输入。如果解析成功,将创建一个ST对象来保存内部解析树表示,否则会产生一个适当的异常。

parser.suite(source)

suite() 函数解析参数 source,就像它是 compile(source, 'file.py', 'exec') 的输入。如果解析成功,将创建一个ST对象来保存内部解析树表示,否则会产生一个适当的异常。

parser.sequence2st(sequence)

此函数接受表示为序列的解析树,并在可能的情况下构建内部表示。如果它可以验证树符合Python语法,并且所有节点都是Python主机版本中的有效节点类型,则从内部表示创建ST对象并将其返回给调用。如果在创建内部表示时存在问题,或者如果无法验证树,则会引发 ParserError 异常。以这种方式创建的ST对象不应假定为正确编译;当ST对象传递给 compilest() 时,编译引起的正常异常仍然可能启动。这可能指示与语法(例如 MemoryError 异常)无关的问题,但也可能是由于诸如解析 del f(0) 的结果,其转义了Python解析器,但是被字节码编译器检查。

表示终端令牌的序列可以表示为 (1, 'name') 形式的两元素列表或者形式为 (1, 'name', 56) 的三元素列表。如果存在第三个元素,则假定它是一个有效的行号。可以为输入树中的终端符号的任何子集指定行号。

parser.tuple2st(sequence)

这与 sequence2st() 功能相同。保留此入口点以实现向后兼容性。

32.1.2. 转换ST对象

ST对象,无论用于创建它们的输入如何,都可以被转换为表示为列表或元组树的分析树,或者可以被编译为可执行代码对象。可以使用或不使用行编号信息来提取解析树。

parser.st2list(st, line_info=False, col_info=False)

此函数接受来自 st 中调用者的ST对象,并返回表示等效解析树的Python列表。所得到的列表表示可以用于检查或以列表形式创建新的解析树。只要存储器可用于构建列表表示,此函数不会失败。如果解析树仅用于检查,则应该使用 st2tuple() 来减少内存消耗和碎片。当需要列表表示时,此函数显着快于检索元组表示并将其转换为嵌套列表。

如果 line_info 为真,则将包括所有终端令牌的行号信息作为表示令牌的列表的第三元素。注意,提供的行号指定了令牌 ends 的行。如果标志为假或省略,则省略此信息。

parser.st2tuple(st, line_info=False, col_info=False)

此函数接受来自 st 中的调用者的ST对象,并返回表示等效解析树的Python元组。除了返回元组而不是列表,此函数与 st2list() 相同。

如果 line_info 为真,则将包括所有终端令牌的行号信息作为表示令牌的列表的第三元素。如果标志为假或省略,则省略此信息。

parser.compilest(st, filename='<syntax-tree>')

Python字节编译器可以在ST对象上调用,以产生代码对象,可以用作调用内置 exec()eval() 函数的一部分。此函数提供了编译器的接口,使用由 filename 参数指定的源文件名将内部解析树从 st 传递到解析器。为 filename 提供的默认值表示源是ST对象。

编译ST对象可能导致与编译相关的异常;一个例子是由 del f(0) 的解析树引起的 SyntaxError:这个语句在Python的正式语法中被认为是合法的,但不是一个法律语言结构。为此条件引发的 SyntaxError 实际上是由Python字节编译器正常生成的,这就是为什么它可以在这一点由 parser 模块引发的原因。编译失败的大多数原因可以通过检查解析树以编程方式诊断。

32.1.3. ST对象上的查询

提供了两个函数,其允许应用程序确定ST是否被创建为表达式或套件。这些函数都不能用于确定是否通过 expr()suite() 从源代码创建ST,或者通过 sequence2st() 从解析树创建ST。

parser.isexpr(st)

st 表示 'eval' 形式时,此函数返回true,否则返回false。这是有用的,因为代码对象通常不能使用现有的内置函数查询此信息。注意,compilest() 创建的代码对象不能像这样查询,并且与由内置 compile() 函数创建的代码对象相同。

parser.issuite(st)

此功能镜像 isexpr(),因为它报告ST对象是否表示 'exec' 形式,通常称为“套件”。假定此函数等效于 not isexpr(st) 是不安全的,因为将来可能支持额外的句法片段。

32.1.4. 异常和错误处理

解析器模块定义单个异常,但也可以从Python运行时环境的其他部分传递其他内置异常。有关可以引发的异常的信息,请参阅每个函数。

exception parser.ParserError

在解析器模块中发生故障时引发异常。这通常用于验证失败,而不是在正常解析期间引发的内置 SyntaxError。异常参数是描述失败原因的字符串或包含导致传递给 sequence2st() 的解析树失败的序列的元组和一个解释字符串。对 sequence2st() 的调用需要能够处理任何类型的异常,而对模块中其他函数的调用只需要知道简单的字符串值。

注意,函数 compilest()expr()suite() 可能引起通常由解析和编译过程引起的异常。这些包括内置异常 MemoryErrorOverflowErrorSyntaxErrorSystemError。在这些情况下,这些例外具有通常与它们相关联的所有含义。有关详细信息,请参阅各功能的说明。

32.1.5. ST对象

在ST对象之间支持有序和等式比较。还支持腌制ST对象(使用 pickle 模块)。

parser.STType

expr()suite()sequence2st() 返回的对象的类型。

ST对象有以下方法:

ST.compile(filename='<syntax-tree>')

compilest(st, filename) 相同。

ST.isexpr()

isexpr(st) 相同。

ST.issuite()

issuite(st) 相同。

ST.tolist(line_info=False, col_info=False)

st2list(st, line_info, col_info) 相同。

ST.totuple(line_info=False, col_info=False)

st2tuple(st, line_info, col_info) 相同。

32.1.6. 示例:compile() 的仿真

虽然许多有用的操作可能发生在解析和字节码生成之间,但最简单的操作是什么都不做。为此,使用 parser 模块产生中间数据结构相当于代码

>>> code = compile('a + 5', 'file.py', 'eval')
>>> a = 5
>>> eval(code)
10

使用 parser 模块的等效操作稍长,并允许将中间内部解析树保留为ST对象:

>>> import parser
>>> st = parser.expr('a + 5')
>>> code = st.compile('file.py')
>>> a = 5
>>> eval(code)
10

需要ST和代码对象的应用程序可以将该代码打包成容易获得的函数:

import parser

def load_suite(source_string):
    st = parser.suite(source_string)
    return st, st.compile()

def load_expression(source_string):
    st = parser.expr(source_string)
    return st, st.compile()