33. Python コンパイラパッケージ¶
バージョン 2.6 で撤廃: compiler
モジュールは Python 3 で削除されました。
Python コンパイラパッケージは Python のソースコードを分析したり Python バイトコードを生成するためのツールです。compiler は Python のソースコードから抽象的な構文木を生成し、その構文木から Python バイトコード (bytecode) を生成するライブラリをそなえています。
compiler
パッケージは、Python で書かれた Python ソースコードからバイトコードへの変換プログラムです。これは組み込みの構文解析器と標準ライブラリの parser
モジュールを使って、具象構文木を生成します。この構文木から抽象構文木 AST (Abstract Syntax Tree) が生成され、その後 Python バイトコードが得られます。
このパッケージの機能は、Python インタプリタに内蔵されている組み込みのコンパイラがすべて含んでいるものです。これはその機能と正確に同じものになるよう意図してつくられています。なぜ同じことをするコンパイラをもうひとつ作る必要があるのでしょうか? このパッケージはいろいろな目的に使うことができるからです。これは組み込みのコンパイラよりも簡単に変更できますし、これが生成する AST は Python ソースコードを解析するのに有用です。
この章では compiler
パッケージのいろいろなコンポーネントがどのように動作するのかを説明します。そのため説明はリファレンスマニュアル的なものと、チュートリアル的な要素がまざったものになっています。
33.1. 基本的なインターフェイス¶
このパッケージのトップレベルでは 4 つの関数が定義されています。 compiler
モジュールを import すると、これらの関数およびこのパッケージに含まれている一連のモジュールが使用可能になります。
-
compiler.
parse
(buf)¶ buf 中の Python ソースコードから得られた抽象構文木 AST を返します。ソースコード中にエラーがある場合、この関数は
SyntaxError
を発生させます。返り値はcompiler.ast.Module
インスタンスであり、この中に構文木が格納されています。
-
compiler.
parseFile
(path)¶ path で指定されたファイル中の Python ソースコードから得られた抽象構文木 AST を返します。これは
parse(open(path).read())
と等価な働きをします。
-
compiler.
walk
(ast, visitor[, verbose])¶ ast に格納された抽象構文木の各ノードを先行順序 (pre-order) でたどっていきます。各ノードごとに visitor インスタンスの該当するメソッドが呼ばれます。
-
compiler.
compile
(source, filename, mode, flags=None, dont_inherit=None)¶ 文字列 source 、Python モジュール、文あるいは式を
exec
文あるいはeval()
関数で実行可能なバイトコードオブジェクトにコンパイルします。この関数は組み込みのcompile()
関数を置き換えるものです。filename は実行時のエラーメッセージに使用されます。
mode は、モジュールをコンパイルする場合は 'exec'、 (対話的に実行される) 単一の文をコンパイルする場合は 'single'、式をコンパイルする場合には 'eval' を渡します。
引数 flags および dont_inherit は将来的に使用される文に影響しますが、いまのところはサポートされていません。
-
compiler.
compileFile
(source)¶ ファイル source をコンパイルし、
.pyc
ファイルを生成します。
compiler
パッケージは以下のモジュールを含んでいます: ast
, consts
, future
, misc
, pyassem
, pycodegen
, symbols
, transformer
, そして visitor
。
33.2. 制限¶
compiler パッケージにはエラーチェックにいくつか問題が存在します。構文エラーはインタープリタの 2 つの別々のフェーズによって認識されます。ひとつはインタープリタのパーザによって認識されるもので、もうひとつはコンパイラによって認識されるものです。 compiler パッケージはインタープリタのパーザに依存しているので、最初の段階のエラーチェックは労せずして実現できています。しかしその次の段階は、実装されてはいますが、その実装は不完全です。たとえば compiler パッケージは引数に同じ名前が 2 度以上出てきていてもエラーを出しません: def f(x, x): ...
compiler の将来のバージョンでは、これらの問題は修正される予定です。
33.3. Python 抽象構文¶
compiler.ast
モジュールは Python の抽象構文木 AST を定義します。 AST では各ノードがそれぞれの構文要素をあらわします。木の根は Module
オブジェクトです。
抽象構文木 AST オブジェクトは、パーズされた Python ソースコードに対する高水準のインターフェイスを提供します。 Python インタプリタにおける parser
モジュールとコンパイラは C で書かれおり、具象構文木を使っています。具象構文木は Python のパーザ中で使われている構文と密接に関連しています。ひとつの要素に単一のノードを割り当てる代わりに、ここでは Python の優先順位に従って、何層にもわたるネストしたノードがしばしば使われています。
抽象構文木 AST は、 compiler.transformer
(変換器) モジュールによって生成されます。 transformer は組み込みの Python パーザに依存しており、これを使って具象構文木をまず生成します。つぎにそこから抽象構文木 AST を生成します。
transformer
モジュールは、実験的な Python-to-C コンパイラ用に Greg Stein と Bill Tutt によって作られました。現行のバージョンではいくつもの修正と改良がなされていますが、抽象構文木 AST と transformer の基本的な構造は Stein と Tutt によるものです。
33.3.1. AST ノード¶
compiler.ast
モジュールは、各ノードのタイプとその要素を記述したテキストファイルからつくられます。各ノードのタイプはクラスとして表現され、そのクラスは抽象基底クラス compiler.ast.Node
を継承し子ノードの名前属性を定義しています。
-
class
compiler.ast.
Node
¶ Node
インスタンスはパーザジェネレータによって自動的に作成されます。ある特定のNode
インスタンスに対する推奨されるインターフェイスとは、子ノードにアクセスするために public な属性を使うことです。 public な属性は単一のノード、あるいは一連のノードのシーケンスに束縛されているかもしれませんが、これはNode
のタイプによって違います。たとえばClass
ノードのbases
属性は基底クラスのノードのリストに束縛されており、doc
属性は単一のノードのみに束縛されている、といった具合です。各
Node
インスタンスはlineno
属性をもっており、これはNone
かもしれません。 XXX どういったノードが使用可能な lineno をもっているかの規則は定かではない。全ての
Node
オブジェクトは以下のメソッドを提供します:-
getChildren
()¶ 子ノードと子オブジェクトを、これらが出てきた順で、平らなリスト形式にして返します。とくにノードの順序は、 Python 文法中に現れるものと同じになっています。すべての子が
Node
インスタンスなわけではありません。たとえば関数名やクラス名といったものは、ただの文字列として表されます。
-
getChildNodes
()¶ 子ノードをこれらが出てきた順で平らなリスト形式にして返します。このメソッドは
getChildren()
に似ていますが、Node
インスタンスしか返さないという点で異なっています。
-
Node
クラスの一般的な構造を説明するため、以下に 2 つの例を示します。 while
文は以下のような文法規則により定義されています:
while_stmt: "while" expression ":" suite
["else" ":" suite]
While
ノードは 3 つの属性をもっています: test
, body
および else_
です。 (ある属性にふさわしい名前が Python の予約語としてすでに使われているとき、その名前を属性名にすることはできません。そのため、ここでは名前が正規のものとして受けつけられるようにアンダースコアを後につけてあります、そのため else_
は else
のかわりです。)
if
文はもっとこみ入っています。なぜならこれはいくつもの条件判定を含む可能性があるからです。
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
If
ノードでは、 tests
および else_
の 2つだけの属性が定義されています。 tests
属性には条件式とその後の動作のタプルがリスト形式で入っています。おのおのの if
/ elif
節ごとに 1 タプルです。各タプルの最初の要素は条件式で、2 番目の要素はもしその式が真ならば実行されるコードをふくんだ Stmt
ノードになっています。
If
の getChildren()
メソッドは、子ノードの平らなリストを返します。 if
/ elif
節が 3 つあって else
節がない場合なら、 getChildren()
は 6 要素のリストを返すでしょう: 最初の条件式、最初の Stmt
、2 番目の条件式…といった具合です。
以下の表は 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 |
拡張 *-引数の値 | |
dstar_args |
拡張 **-引数の値 | |
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 |
Decorators または None |
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
は、次のうちどれかのクラスになります: AssAttr
, AssList
, AssName
または AssTuple
。
代入対象の各ノードには代入されるオブジェクトの種類が記録されています。 AssName
は a = 1
などの単純な変数名、 AssAttr
は a.x = 1
などの属性に対する代入、 AssList
および AssTuple
はそれぞれ、 a, b, c = a_tuple
などのようなリストとタプルの展開をあらわします。
代入対象ノードはまた、そのノードが代入で使われるのか、それとも削除文で使われるのかをあらわす属性 flags
も持っています。 AssName
は del x
などのような削除文をあらわすのにも使われます。
ある式がいくつかの属性への参照をふくんでいるときは、代入あるいは削除文はただひとつだけの AssAttr
ノードをもちます – 最終的な属性への参照としてです。それ以外の属性への参照は AssAttr
インスタンスの expr
属性にある Getattr
ノードによってあらわされます。
33.3.3. 例¶
この節では、Python ソースコードに対する抽象構文木 AST のかんたんな例をいくつかご紹介します。これらの例では parse()
関数をどうやって使うか、AST の repr 表現はどんなふうになっているか、そしてある AST ノードの属性にアクセスするにはどうするかを説明します。
最初のモジュールでは単一の関数を定義しています。かりにこれは /tmp/doublelib.py
に格納されていると仮定しましょう。
"""This is an example module.
This is the docstring.
"""
def double(x):
"Return twice the argument"
return x * 2
以下の対話的インタプリタのセッションでは、見やすさのため長い AST の repr を整形しなおしてあります。 AST の repr では qualify されていないクラス名が使われています。 repr 表現からインスタンスを作成したい場合は、 compiler.ast
モジュールからそれらのクラス名を import しなければなりません。
>>> 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. Visitor を使って AST をわたり歩く¶
visitor パターンは… compiler
パッケージは、Python のイントロスペクション機能を利用して visitor のために必要な大部分のインフラを省略した、visitor パターンの変種を使っています。
visit されるクラスは、visitor を受け入れるようにプログラムされている必要はありません。 visitor が必要なのはただそれがとくに興味あるクラスに対して visit メソッドを定義することだけです。それ以外はデフォルトの visit メソッドが処理します。
XXX visitor 用の魔法の visit()
メソッド。
-
compiler.visitor.
walk
(tree, visitor[, verbose])¶
-
class
compiler.visitor.
ASTVisitor
¶ ASTVisitor
は構文木を正しい順序でわたり歩くようにします。それぞれのノードはまずpreorder()
の呼び出しではじまります。各ノードに対して、これは 'visitNodeType' という名前のメソッドに対するpreorder()
関数への visitor 引数をチェックします。ここで NodeType の部分はそのノードのクラス名です。たとえばWhile
ノードなら、visitWhile()
が呼ばれるわけです。もしそのメソッドが存在している場合、それはそのノードを第一引数として呼び出されます。ある特定のノード型に対する visitor メソッドでは、その子ノードをどのようにわたり歩くかが制御できます。
ASTVisitor
は visitor に visit メソッドを追加することで、その visitor 引数を修正します; このメソッドは特定の子ノードを訪問するのに使われるでしょう。特定のノード型に対する visitor が存在しない場合、default()
メソッドが呼び出されます。ASTVisitor
オブジェクトには以下のようなメソッドがあります:XXX 追加の引数を記述
-
default
(node[, ...])¶
-
dispatch
(node[, ...])¶
-
preorder
(tree, visitor)¶
-
33.5. バイトコード生成¶
バイトコード生成器はバイトコードを出力する visitor です。 visit メソッドが呼ばれるたびにこれは emit()
メソッドを呼び出し、バイトコードを出力します。基本的なバイトコード生成器はモジュール、クラス、および関数のために特殊化されます。アセンブラがこれらの出力された命令を低レベルのバイトコードに変換します。これはコードオブジェクトからなる定数のリスト生成や、分岐のオフセット計算といった処理をおこないます。