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'表单。

  • parser. expr(* source *)

    • expr()函数将参数* source *解析为compile(source, 'file.py', 'eval')的 Importing。如果解析成功,则将创建一个 ST 对象来保存内部解析树表示形式,否则将引发适当的异常。
  • parser. suite(* source *)

    • suite()函数将参数* source *解析为compile(source, 'file.py', 'exec')的 Importing。如果解析成功,则将创建一个 ST 对象来保存内部解析树表示形式,否则将引发适当的异常。
  • parser. sequence2st(序列)

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

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

  • parser. tuple2st(序列)
    • 该Function与sequence2st()相同。维护该入口点是为了向后兼容。

32.1.2. 转换 ST 对象

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

  • parser. st2list(* ast * [,* line_info *])
    • 此函数在* ast *中接受来自调用者的 ST 对象,并返回表示等效解析树的 Python 列表。生成的列表表示形式可用于检查或以列表形式创建新的解析树。只要有可用的内存来构建列表表示,此Function就不会失败。如果解析树仅用于检查,则应使用st2tuple()来减少内存消耗和碎片。当需要列表表示形式时,此Function比检索 Tuples 表示形式并将其转换为嵌套列表要快得多。

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

  • parser. st2tuple(* ast * [,* line_info *])
    • 此函数在* ast *中接受来自调用者的 ST 对象,并返回表示等效解析树的 PythonTuples。除了返回 Tuples 而不是列表之外,此函数与st2list()相同。

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

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

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

32.1.3. 关于 ST 对象的查询

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

  • parser. isexpr(* ast *)
    • 当* ast *表示'eval'形式时,此函数返回 true,否则返回 false。这很有用,因为通常无法使用现有内置函数来查询代码对象的信息。注意,由compilest()创建的代码对象也不能像这样查询,并且与内置compile()函数创建的代码对象相同。
  • parser. issuite(* ast *)
    • 此函数镜像isexpr(),因为它报告 ST 对象是否表示'exec'形式(通常称为“套件”)。不安全地假定此函数等效于not isexpr(ast),因为将来可能会支持其他语法片段。

32.1.4. 异常和错误处理

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

  • exception parser. ParserError
    • 解析器模块内发生故障时引发异常。通常是为验证失败而生成的,而不是在正常解析过程中引发的内置SyntaxError。异常参数可以是描述失败原因的字符串,也可以是包含从传递到sequence2st()的解析树中导致失败的序列的 Tuples 和说明性字符串。对sequence2st()的调用需要能够处理这两种类型的异常,而对模块中其他函数的调用仅需要知道简单的字符串值即可。

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

32.1.5. ST 对象

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

ST 对象具有以下方法:

  • ST. compile([文件名])

    • 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. 示例:仿真 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()