33. Python 编译器包 — Python 文档

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

33. Python编译器包

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


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

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

该包的完整功能复制了 Python 解释器提供的内置编译器。 它旨在几乎完全匹配其行为。 为什么要实现另一个做同样事情的编译器? 该软件包可用于多种用途。 它可以比内置编译器更容易修改。 它生成的 AST 可用于分析 Python 源代码。

本章解释了 compiler 包的各个组件如何工作。 它融合了参考资料和教程。

33.1. 基本界面

包的顶层定义了四个函数。 如果您导入 compiler,您将获得这些函数以及包含在包中的模块集合。

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() 函数。

文件名 将用于运行时错误消息。

mode 必须是 'exec' 编译模块,'single' 编译单个(交互式)语句,或 'eval' 编译表达式。

flagsdont_inherit 参数影响与未来相关的语句,但尚不支持。

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

compiler 包包含以下模块:astconstsfuturemiscpyassempycodegensymbolstransformervisitor


33.2. 限制

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

编译器的未来版本应该解决这些问题。


33.3. Python 抽象语法

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

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

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

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

33.3.1. AST 节点

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

class compiler.ast.Node

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

每个 Node 实例都有一个 lineno 属性,可能是 None。 XXX 不确定哪些节点将具有有用的 lineno 的规则。

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

getChildren()

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

getChildNodes()

按子节点出现的顺序返回子节点的扁平列表。 该方法类似于 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 子句对应一对。 该对的第一个元素是测试表达式。 第二个元素是一个 Stmt 节点,它包含测试为真时要执行的代码。

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

下表列出了 compiler.ast 中定义的每个 Node 子类及其实例上可用的每个公共属性。 大多数属性的值本身就是 Node 实例或实例序列。 当值不是实例时,类型会在注释中注明。 属性按 getChildren()getChildNodes() 返回的顺序列出。

节点类型 属性 价值
Add left 左操作数
right 右操作数
And nodes 操作数列表
AssAttr 作为分配目标的属性
expr 点左侧的表达式
attrname 属性名称,一个字符串
flags XXX
AssList nodes 分配给的列表元素列表
AssName name 名称被分配给
flags XXX
AssTuple nodes 被分配给的元组元素列表
Assert test 要测试的表达式
fail AssertionError 的值
Assign nodes 分配目标列表,每个等号一个
expr 被赋值的值
AugAssign node
op
expr
Backquote expr
Bitand nodes
Bitor nodes
Bitxor nodes
Break
CallFunc node 被调用者的表达式
args 参数列表
star_args 扩展的 *-arg 值
dstar_args 扩展的 **-arg 值
Class name 类的名称,一个字符串
bases 基类列表
doc 文档字符串、字符串或 None
code 类语句的主体
Compare expr
ops
Const value
Continue
Decorators nodes 函数装饰器表达式列表
Dict items
Discard expr
Div left
right
Ellipsis
Expression node
Exec expr
locals
globals
FloorDiv left
right
For assign
list
body
else_
From modname
names
Function decorators DecoratorsNone
name def 中使用的名称,一个字符串
argnames 参数名称列表,作为字符串
defaults 默认值列表
flags xxx
doc 文档字符串、字符串或 None
code 函数体
GenExpr code
GenExprFor assign
iter
ifs
GenExprIf test
GenExprInner expr
quals
Getattr expr
attrname
Global names
If tests
else_
Import names
Invert expr
Keyword name
expr
Lambda argnames
defaults
flags
code
LeftShift left
right
List nodes
ListComp expr
quals
ListCompFor assign
list
ifs
ListCompIf test
Mod left
right
Module doc 文档字符串、字符串或 None
node 模块主体,一个 Stmt
Mul left
right
Name name
Not expr
Or nodes
Pass
Power left
right
Print nodes
dest
Printnl nodes
dest
Raise expr1
expr2
expr3
Return value
RightShift left
right
Slice expr
flags
lower
upper
Sliceobj nodes 声明清单
Stmt nodes
Sub left
right
Subscript expr
flags
subs
TryExcept body
handlers
else_
TryFinally body
final
Tuple nodes
UnaryAdd expr
UnarySub expr
While test
body
else_
With expr
vars
body
Yield value


33.3.2. 分配节点

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

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

目标赋值节点还有一个 flags 属性,指示该节点是用于赋值还是在删除语句中。 AssName 也用于表示删除语句,例如 del x

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


33.3.3. 例子

本节展示了 Python 源代码的 AST 的几个简单示例。 这些示例演示了如何使用 parse() 函数、AST 的 repr 是什么样的,以及如何访问 AST 节点的属性。

第一个模块定义了一个函数。 假设它存储在 doublelib.py 中。

"""This is an example module.

This is the docstring.
"""

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

在下面的交互式解释器会话中,我重新格式化了长 AST 代表以提高可读性。 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 的自省功能来消除对大部分访问者基础设施的需求。

被访问的课程不需要编程来接受访问者。 访问者只需为它特别感兴趣的类定义访问方法; 默认访问方法可以处理其余的。

XXX 访问者的神奇 visit() 方法。

compiler.visitor.walk(tree, visitor[, verbose])
class compiler.visitor.ASTVisitor

ASTVisitor 负责以正确的顺序遍历树。 步行从调用 preorder() 开始。 对于每个节点,它检查 preorder()visitor 参数以获取名为“visitNodeType”的方法,其中 NodeType 是节点类的名称,例如 对于 While 节点,将调用 visitWhile()。 如果该方法存在,则将节点作为其第一个参数调用它。

特定节点类型的访问者方法可以控制在遍历期间如何访问子节点。 ASTVisitor 修改了visitor 参数,给visitor 添加了visit 方法; 此方法可用于访问特定的子节点。 如果没有找到特定节点类型的访问者,则调用 default() 方法。

ASTVisitor 对象有以下方法:

XXX 描述额外的参数

default(node[, ...])
dispatch(node[, ...])
preorder(tree, visitor)


33.5. 字节码生成

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