On this page
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
实例。
- 返回* buf *中 Python 源代码的抽象语法树。如果源代码中有错误,该函数将引发SyntaxError。返回值是包含树的
compiler.
parseFile
(* path *)- 在* path *指定的文件中返回 Python 源代码的抽象语法树。它等效于
parse(open(path).read())
。
- 在* path *指定的文件中返回 Python 源代码的抽象语法树。它等效于
compiler.
walk
(* ast , visitor * [,* verbose *])- 在抽象语法树* ast 上进行预订。在 visitor *实例上为遇到的每个节点调用适当的方法。
compiler.
compile
(* source , filename , mode , flags = None , dont_inherit = None *)
filename *将用于运行时错误消息。
mode *必须为“ exec”以编译模块,为“ single”以编译单个(交互式)语句,或为“ eval”以编译表达式。
flags 和* dont_inherit *参数影响与 Future 相关的语句,但尚不支持。
compiler.
compileFile
(* source *)- 编译文件* source *并生成一个.pyc 文件。
compiler软件包包含以下模块:ast,consts
,future
,misc
,pyassem
,pycodegen
,symbols
,transformer
和visitor
。
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实例都有一个lineno
属性,该属性可以是None
。 XXX 不确定哪些节点具有有效的线路编号的规则是什么。
所有Node对象都提供以下方法:
getChildren
( )- 返回子节点和对象按其出现 Sequences 的扁平列表。具体来说,节点的 Sequences 是它们在 Python 语法中出现的 Sequences。并非所有的孩子都是Node个实例。函数和类的名称例如是纯字符串。
getChildNodes
( )- 返回子节点按其出现的 Sequences 的扁平列表。此方法类似于getChildren(),除了它只返回属于Node实例的那些子代。
两个示例说明了Node类的一般结构。 while语句由以下语法生成定义:
while_stmt: "while" expression ":" suite
["else" ":" suite]
While
节点具有三个属性:test,body
和else_
。 (如果属性的自然名称也是 Python 保留的单词,则不能将其用作属性名称.在该单词上附加下划线以使其成为合法标识符,因此是else_
而不是else。)
if语句更复杂,因为它可以包含多个测试。
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
If
节点仅定义两个属性:tests
和else_
。 tests
属性是测试表达式的序列,因此是主体对。每个if/elif子句Pair。该对中的第一个元素是测试表达式。第二个元素是Stmt
节点,其中包含测试为 true 时要执行的代码。
If
的getChildren()
方法返回平面的子节点列表。如果有三个if/elif子句而没有else子句,则getChildren()
将返回六个元素的列表:第一个测试表达式,第一个Stmt
,第二个文本表达式,等等。
下表列出了compiler.ast中定义的每个Node子类以及其实例上可用的每个公共属性。大多数属性的值本身就是Node个实例或实例序列。如果值是实例以外的值,则在 Comments 中注明类型。按getChildren()
和getChildNodes()
返回属性的 Sequences 列出属性。
Node type | Attribute | Value |
---|---|---|
Add |
left |
left operand |
right |
right operand | |
And |
nodes |
操作数列表 |
AssAttr |
属性作为分配目标 | |
expr |
点左侧的表达式 | |
attrname |
属性名称,字符串 | |
flags |
XXX | |
AssList |
nodes |
分配给的列表元素的列表 |
AssName |
name |
名称被分配给 |
flags |
XXX | |
AssTuple |
nodes |
分配给的 Tuples 元素列表 |
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 |
Base Class 列表 | |
doc |
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 |
Decorators 或None |
name |
def 中使用的名称,字符串 | |
argnames |
参数名称列表,以字符串形式 | |
defaults |
默认值列表 | |
flags |
xxx | |
doc |
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 |
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 |
StatementsLists |
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将是以下类别之一:AssAttr
,AssList
,AssName
或AssTuple
。
每个目标分配节点都将描述分配给以下对象的类型:AssName
为简单名称,例如a = 1
。 AssAttr
用于分配的属性,例如a.x = 1
。 AssList
和AssTuple
分别用于列表和 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()
。如果该方法存在,则以节点作为其第一个参数调用它。
- ASTVisitor负责以正确的 Sequences 在树上行走。步行始于对preorder()的呼叫。对于每个节点,它会检查preorder()的* visitor *参数以查找名为“ visitNodeType”的方法,其中 NodeType 是节点类的名称,例如对于
特定节点类型的 visitor 方法可以控制步行过程中如何访问子节点。 ASTVisitorpass向访问者添加访问方法来修改访问者参数;此方法可用于访问特定的子节点。如果找不到特定节点类型的访问者,则调用default()方法。
ASTVisitor对象具有以下方法:
XXX 描述额外的论点
default
(* node * [,* ... *])dispatch
(* node * [,* ... *])preorder
(* tree , visitor *)
33.5. 字节码生成
代码生成器是发出字节码的访问者。每个访问方法都可以调用emit()
方法以发出新的字节码。基本代码生成器专门用于模块,类和Function。汇编器将发出的指令转换为低级字节码格式。它处理诸如生成代码对象的常量列表和计算跳转偏移之类的事情。