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

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

Note

从 Python 2.5 开始,使用ast模块在 Abstract Syntax Tree(AST)生成和编译阶段插入更为方便。

parser模块导出此处记录的名称,也将“ st”替换为“ ast”;这是从没有其他 AST 的时代开始的 Legacy,与 Python 2.5 中的 AST 无关。这也是将函数的关键字参数称为* ast 而不是 st *的原因。 “ ast”函数已在 Python 3 中删除。

关于此模块,需要注意一些事项,这些事项对于使用创建的数据结构非常重要。这不是有关为 Python 代码编辑分析树的教程,而是提供了一些使用parser模块的示例。

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

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

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

不需要 ST 对象来支持此模块的Function,而是出于以下三个目的提供 ST 对象:允许应用程序分摊处理复杂解析树的成本;提供与 Python 相比节省内存空间的解析树表示形式列表或 Tuples 表示形式,并简化了用 C 操纵解析树的其他模块的创建。可以在 Python 中创建一个简单的“包装”类,以隐藏 ST 对象的使用。

parser模块为一些不同的目的定义Function。最重要的目的是创建 ST 对象并将 ST 对象转换为其他表示形式,例如解析树和已编译的代码对象,但是还有一些Function可以查询由 ST 对象表示的解析树的类型。

See also

  • Module symbol

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

  • Module token

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

32.1.1. 创建 ST 对象

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

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

32.1.2. 转换 ST 对象

ST 对象,无论用于创建它们的 Importing 如何,都可以转换为表示为列表树或 Tuples 树的解析树,或者可以编译为可执行代码对象。可以在有或没有行编号信息的情况下提取解析树。

如果* line_info 为 true,则会将所有终端令牌的行号信息作为代表令牌的列表的第三个元素包括在内。请注意,提供的行号指定了令牌 end *所在的行。如果标志为 false 或Ellipsis,则忽略此信息。

如果* line_info *为 true,则会将所有终端令牌的行号信息作为代表令牌的列表的第三个元素包括在内。如果标志为 false 或Ellipsis,则忽略此信息。

编译 ST 对象可能会导致与编译有关的异常。例如del f(0)的解析树引起的SyntaxError:此语句在 Python 的正式语法中被认为是合法的,但不是合法的语言构造。实际上针对这种情况引发的SyntaxError实际上是由 Python 字节编译器生成的,这就是为什么现在可以passparser模块将其引发的原因。编译失败的大多数原因可以pass检查解析树来以编程方式诊断。

32.1.3. 关于 ST 对象的查询

提供了两个Function,这些Function使应用程序可以确定将 ST 创建为表达式还是套件。这两个函数均不能用来确定 ST 是passexpr()suite()是从源代码创建的,还是passsequence2st()是从解析树创建的。

32.1.4. 异常和错误处理

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

请注意,函数compilest()expr()suite()可能会引发通常由解析和编译过程引发的异常。这些包括内置的异常MemoryErrorOverflowErrorSyntaxErrorSystemError。在这些情况下,这些异常具有通常与其相关的所有含义。有关详细信息,请参阅每个Function的说明。

32.1.5. ST 对象

ST 对象之间支持有序和相等性比较。还支持对 ST 对象进行 Pickling(使用pickle模块)。

ST 对象具有以下方法:

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()
首页