32.1. parser — 访问 Python 解析树 — Python 文档

来自菜鸟教程
Python/docs/2.7/library/parser
跳转至:导航、​搜索

32.1. 解析器 — 访问 Python 解析树

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

笔记

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

parser 模块导出此处记录的名称,其中“st”替换为“ast”; 这是当时没有其他 AST 并且与 Python 2.5 中的 AST 无关的遗产。 这也是函数的关键字参数被称为 ast 而不是 st 的原因。 “ast”函数已在 Python 3 中删除。


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

最重要的是,需要很好地理解内部解析器处理的 Python 语法。 有关语言语法的完整信息,请参阅 Python 语言参考 。 解析器本身是根据标准 Python 发行版中的文件 Grammar/Grammar 中定义的语法规范创建的。 存储在由该模块创建的 ST 对象中的解析树是由 expr()suite() 函数创建时内部解析器的实际输出,如下所述。 sequence2st() 创建的 ST 对象忠实地模拟了这些结构。 请注意,随着 Python 的正式语法的修订,被认为“正确”的序列的值会因 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 对象表示的解析树类型的函数。

也可以看看

模块符号
表示解析树内部节点的有用常量。
模块 代币
表示分析树的叶节点的有用常量和用于测试节点值的函数。


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(ast[, line_info])

此函数从 ast 中的调用者接收 ST 对象,并返回表示等效解析树的 Python 列表。 结果列表表示可用于检查或以列表形式创建新的解析树。 只要内存可用于构建列表表示,此函数就不会失败。 如果解析树仅用于检查,则应改用 st2tuple() 以减少内存消耗和碎片。 当需要列表表示时,此函数比检索元组表示并将其转换为嵌套列表要快得多。

如果 line_info 为真,则所有终端标记的行号信息将作为表示标记的列表的第三个元素包含在内。 请注意,提供的行号指定令牌 结束的行 。 如果标志为假或省略,则省略此信息。

parser.st2tuple(ast[, line_info])

该函数从 ast 中的调用者接收一个 ST 对象,并返回一个表示等效解析树的 Python 元组。 除了返回元组而不是列表之外,此函数与 st2list() 相同。

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

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

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

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


32.1.3. ST 对象查询

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

parser.isexpr(ast)
ast代表一个'eval'形式时,该函数返回真,否则返回假。 这很有用,因为通常无法使用现有的内置函数查询代码对象以获取此信息。 请注意,compilest()创建的代码对象也不能这样查询,与内置的compile()函数创建的代码对象相同。
parser.issuite(ast)
该函数反映了 isexpr(),因为它报告 ST 对象是否代表 'exec' 形式,通常称为“套件”。 假设此函数等同于 not isexpr(ast) 是不安全的,因为将来可能会支持其他语法片段。


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])
compilest(st, filename) 相同。
ST.isexpr()
isexpr(st) 相同。
ST.issuite()
issuite(st) 相同。
ST.tolist([line_info])
st2list(st, line_info) 相同。
ST.totuple([line_info])
st2tuple(st, line_info) 相同。


32.1.6. 示例:模拟编译()

虽然在解析和字节码生成之间可能会发生许多有用的操作,但最简单的操作是什么都不做。 为此,使用 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()