33. Python 编译器套件

自 2.6 版起弃用:compiler软件包已在 Python 3 中删除。

Python 编译器软件包是用于分析 Python 源代码并生成 Python 字节码的工具。编译器包含用于从 Python 源代码生成抽象语法树并从树生成 Python bytecode的库。

compiler包是使用 Python 编写的字节码转换器的 Python 源。它使用内置的解析器和标准parser模块生成具体的语法树。该树用于生成抽象语法树(AST),然后生成 Python 字节码。

该软件包的全部Function与 Python 解释器提供的内置编译器相同。它旨在几乎完全匹配其行为。为什么要实现另一个执行相同Function的编译器?该软件包可用于多种用途。它比内置编译器更容易修改。它生成的 AST 对于分析 Python 源代码很有用。

本章说明compiler软件包的各个组件如何工作。它将参考资料与教程融合在一起。

33.1. 基本界面

程序包的顶层定义了四个Function。如果导入compiler,则将获得这些Function以及包装中包含的模块的集合。

  • compiler. parse(* buf *)

    • 返回* buf *中 Python 源代码的抽象语法树。如果源代码中有错误,该函数将引发SyntaxError。返回值是包含树的compiler.ast.Module实例。
  • compiler. parseFile(* path *)

    • 在* path *指定的文件中返回 Python 源代码的抽象语法树。它等效于parse(open(path).read())
  • compiler. walk(* ast visitor * [,* verbose *])

    • 在抽象语法树* ast 上进行预订。在 visitor *实例上为遇到的每个节点调用适当的方法。
  • compiler. compile(* source filename mode flags = None dont_inherit = None *)

    • 将字符串* source *(Python 模块,语句或表达式)编译为可由 exec 语句或eval()执行的代码对象。此函数替代了内置的compile()函数。
  • filename *将用于运行时错误消息。

  • mode *必须为“ exec”以编译模块,为“ single”以编译单个(交互式)语句,或为“ eval”以编译表达式。

  • flags 和* dont_inherit *参数影响与 Future 相关的语句,但尚不支持。

  • compiler. compileFile(* source *)
    • 编译文件* source *并生成一个.pyc 文件。

compiler软件包包含以下模块:astconstsfuturemiscpyassempycodegensymbolstransformervisitor

33.2. Limitations

编译器程序包的错误检查存在一些问题。解释器在两个不同的阶段检测语法错误。一组错误由解释程序的解析器检测,另一组错误由编译器检测。编译器程序包依赖于解释器的解析器,因此它免费获得错误检查的第一阶段。它本身实现了第二阶段,并且实现不完整。例如,如果名称在参数列表中多次出现,则编译器程序包不会引发错误:def f(x, x): ...

编译器的 Future 版本应解决这些问题。

33.3. Python 抽象语法

compiler.ast模块为 Python 定义了一种抽象语法。在抽象语法树中,每个节点代表一个语法构造。树的根是Module对象。

抽象语法为解析的 Python 源代码提供了更高级别的接口。 parser模块和用 C 编写的 Python 解释器编译器使用具体的语法树。具体语法与用于 Python 解析器的语法描述紧密相关。代替构造的单个节点,Python 的优先级规则通常引入了多层嵌套节点。

抽象语法树由compiler.transformer模块创建。转换器依赖于内置的 Python 解析器来生成具体的语法树。它从具体树生成抽象语法树。

transformer模块由 Greg Stein 和 Bill Tutt 创建,用于实验性的 Python 到 C 的编译器。当前版本包含许多修改和改进,但是抽象语法和转换器的基本形式归功于 Stein 和 Tutt。

33.3.1. AST 节点

compiler.ast模块是从描述每个节点类型及其元素的文本文件生成的。每个节点类型都表示为一个类,该类从抽象 Base Classcompiler.ast.Node继承,并为子节点定义了一组命名属性。

  • 类别 compiler.ast. Node
    • Node实例由解析器生成器自动创建。对于特定的Node实例,建议使用的接口是使用公共属性访问子节点。取决于Node类型,公共属性可以绑定到单个节点或一系列节点。例如,Class节点的bases属性绑定到 Base Class 节点列表,而doc属性绑定到单个节点。

每个Node实例都有一个lineno属性,该属性可以是None。 XXX 不确定哪些节点具有有效的线路编号的规则是什么。

所有Node对象都提供以下方法:

  • getChildren ( )

    • 返回子节点和对象按其出现 Sequences 的扁平列表。具体来说,节点的 Sequences 是它们在 Python 语法中出现的 Sequences。并非所有的孩子都是Node个实例。函数和类的名称例如是纯字符串。
  • getChildNodes ( )

    • 返回子节点按其出现的 Sequences 的扁平列表。此方法类似于getChildren(),除了它只返回属于Node实例的那些子代。

两个示例说明了Node类的一般结构。 while语句由以下语法生成定义:

while_stmt:     "while" expression ":" suite
               ["else" ":" suite]

While节点具有三个属性:testbodyelse_。 (如果属性的自然名称也是 Python 保留的单词,则不能将其用作属性名称.在该单词上附加下划线以使其成为合法标识符,因此是else_而不是else。)

if语句更复杂,因为它可以包含多个测试。

if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]

If节点仅定义两个属性:testselse_tests属性是测试表达式的序列,因此是主体对。每个if/elif子句Pair。该对中的第一个元素是测试表达式。第二个元素是Stmt节点,其中包含测试为 true 时要执行的代码。

IfgetChildren()方法返回平面的子节点列表。如果有三个if/elif子句而没有else子句,则getChildren()将返回六个元素的列表:第一个测试表达式,第一个Stmt,第二个文本表达式,等等。

下表列出了compiler.ast中定义的每个Node子类以及其实例上可用的每个公共属性。大多数属性的值本身就是Node个实例或实例序列。如果值是实例以外的值,则在 Comments 中注明类型。按getChildren()getChildNodes()返回属性的 Sequences 列出属性。

Node typeAttributeValue
Addleftleft operand
rightright operand
Andnodes操作数列表
AssAttr属性作为分配目标
expr点左侧的表达式
attrname属性名称,字符串
flagsXXX
AssListnodes分配给的列表元素的列表
AssNamename名称被分配给
flagsXXX
AssTuplenodes分配给的 Tuples 元素列表
Asserttest要测试的表达式
failAssertionError的值
Assignnodes分配目标的列表,每个等号
expr被赋值
AugAssignnode
op
expr
Backquoteexpr
Bitandnodes
Bitornodes
Bitxornodes
Break
CallFuncnode被呼叫者的表情
args参数列表
star_args扩展的* -arg 值
dstar_args扩展的**-arg 值
Classname类的名称,一个字符串
basesBase Class 列表
docdoc 字串,字串或None
code类语句的主体
Compareexpr
ops
Constvalue
Continue
Decoratorsnodes函数装饰器表达式列表
Dictitems
Discardexpr
Divleft
right
Ellipsis
Expressionnode
Execexpr
locals
globals
FloorDivleft
right
Forassign
list
body
else_
Frommodname
names
FunctiondecoratorsDecoratorsNone
namedef 中使用的名称,字符串
argnames参数名称列表,以字符串形式
defaults默认值列表
flagsxxx
docdoc 字串,字串或None
code函数的主体
GenExprcode
GenExprForassign
iter
ifs
GenExprIftest
GenExprInnerexpr
quals
Getattrexpr
attrname
Globalnames
Iftests
else_
Importnames
Invertexpr
Keywordname
expr
Lambdaargnames
defaults
flags
code
LeftShiftleft
right
Listnodes
ListCompexpr
quals
ListCompForassign
list
ifs
ListCompIftest
Modleft
right
Moduledocdoc 字串,字串或None
node模块的主体,Stmt
Mulleft
right
Namename
Notexpr
Ornodes
Pass
Powerleft
right
Printnodes
dest
Printnlnodes
dest
Raiseexpr1
expr2
expr3
Returnvalue
RightShiftleft
right
Sliceexpr
flags
lower
upper
SliceobjnodesStatementsLists
Stmtnodes
Subleft
right
Subscriptexpr
flags
subs
TryExceptbody
handlers
else_
TryFinallybody
final
Tuplenodes
UnaryAddexpr
UnarySubexpr
Whiletest
body
else_
Withexpr
vars
body
Yieldvalue

33.3.2. 分配节点

有一组用于表示分配的节点。源代码中的每个赋值语句都成为 AST 中的单个Assign节点。 nodes属性是一个列表,其中包含每个分配目标的节点。这是必要的,因为可以将分配链接起来,例如a = b = 2。列表中的每个Node将是以下类别之一:AssAttrAssListAssNameAssTuple

每个目标分配节点都将描述分配给以下对象的类型:AssName为简单名称,例如a = 1AssAttr用于分配的属性,例如a.x = 1AssListAssTuple分别用于列表和 Tuples 扩展,例如a, b, c = a_tuple

目标分配节点还具有flags属性,该属性指示该节点是用于分配还是在删除语句中使用。 AssName还用于表示删除语句,例如del x

当一个表达式包含多个属性引用时,赋值或删除语句将仅包含一个AssAttr节点-用于finally属性引用。其他属性引用将在AssAttr实例的expr属性中表示为Getattr个节点。

33.3.3. Examples

本节显示了一些简单的 AST for Python 源代码示例。这些示例演示了如何使用parse()函数,AST 的代表形式以及如何访问 AST 节点的属性。

第一个模块定义单个Function。假设它存储在doublelib.py中。

"""This is an example module.

This is the docstring.
"""

def double(x):
    "Return twice the argument"
    return x * 2

在下面的交互式解释器会话中,我重新设置了较长的 AST reprs 的可读性。 AST 代表使用不合格的类名。如果要从 repr 创建实例,则必须从compiler.ast模块导入类名。

>>> import compiler
>>> mod = compiler.parseFile("doublelib.py")
>>> mod
Module('This is an example module.\n\nThis is the docstring.\n',
       Stmt([Function(None, 'double', ['x'], [], 0,
                      'Return twice the argument',
                      Stmt([Return(Mul((Name('x'), Const(2))))]))]))
>>> from compiler.ast import *
>>> Module('This is an example module.\n\nThis is the docstring.\n',
...    Stmt([Function(None, 'double', ['x'], [], 0,
...                   'Return twice the argument',
...                   Stmt([Return(Mul((Name('x'), Const(2))))]))]))
Module('This is an example module.\n\nThis is the docstring.\n',
       Stmt([Function(None, 'double', ['x'], [], 0,
                      'Return twice the argument',
                      Stmt([Return(Mul((Name('x'), Const(2))))]))]))
>>> mod.doc
'This is an example module.\n\nThis is the docstring.\n'
>>> for node in mod.node.nodes:
...     print node
...
Function(None, 'double', ['x'], [], 0, 'Return twice the argument',
         Stmt([Return(Mul((Name('x'), Const(2))))]))
>>> func = mod.node.nodes[0]
>>> func.code
Stmt([Return(Mul((Name('x'), Const(2))))])

33.4. 使用访客走 AST

访问者模式是…compiler包在访问者模式上使用了一个变体,该变体利用了 Python 的自省Function,从而消除了对大量访问者基础结构的需求。

所访问的类无需编程即可接受访问者。访问者只需为其特别感兴趣的类定义访问方法;默认的访问方法可以处理其余的操作。

XXX 访客神奇的visit()方法。

  • compiler.visitor. walk(* tree visitor * [,* verbose *])

  • 类别 compiler.visitor. ASTVisitor

    • ASTVisitor负责以正确的 Sequences 在树上行走。步行始于对preorder()的呼叫。对于每个节点,它会检查preorder()的* visitor *参数以查找名为“ visitNodeType”的方法,其中 NodeType 是节点类的名称,例如对于While节点,将调用visitWhile()。如果该方法存在,则以节点作为其第一个参数调用它。

特定节点类型的 visitor 方法可以控制步行过程中如何访问子节点。 ASTVisitorpass向访问者添加访问方法来修改访问者参数;此方法可用于访问特定的子节点。如果找不到特定节点类型的访问者,则调用default()方法。

ASTVisitor对象具有以下方法:

XXX 描述额外的论点

  • default(* node * [,* ... *])

  • dispatch(* node * [,* ... *])

  • preorder(* tree visitor *)

33.5. 字节码生成

代码生成器是发出字节码的访问者。每个访问方法都可以调用emit()方法以发出新的字节码。基本代码生成器专门用于模块,类和Function。汇编器将发出的指令转换为低级字节码格式。它处理诸如生成代码对象的常量列表和计算跳转偏移之类的事情。