バージョン 2.6 で撤廃: compiler パッケージは Python 3.0 で削除されました。
Python compiler パッケージは Python のソースコードを分析したり Python バイトコードを生成するためのツールです。compiler は Python のソースコードから抽象的な構文木を生成し、その構文木から Python バイトコード (bytecode) を生成するライブラリをそなえています。
compiler パッケージは、Python で書かれた Python ソースコードからバイトコードへの変換プログラムです。これは組み込みの構文解析器をつかい、そこで得られた具体的な構文木に対して標準的な parser モジュールを使用します。この構文木から抽象構文木 AST (Abstract Syntax Tree) が生成され、その後 Python バイトコードが得られます。
このパッケージの機能は、Python インタプリタに内蔵されている組み込みのコンパイラがすべて含んでいるものです。これはその機能と正確に同じものになるよう意図してつくられています。なぜ同じことをするコンパイラをもうひとつ作る必要があるのでしょうか? このパッケージはいろいろな目的に使うことができるからです。これは組み込みのコンパイラよりも簡単に変更できますし、これが生成する AST は Python ソースコードを解析するのに有用です。
この章では compiler パッケージのいろいろなコンポーネントがどのように動作するのかを説明します。そのため説明はリファレンスマニュアル的なものと、チュートリアル的な要素がまざったものになっています。
このパッケージのトップレベルでは4つの関数が定義されています。 compiler モジュールを import すると、これらの関数およびこのパッケージに含まれている一連のモジュールが使用可能になります。
buf 中の Python ソースコードから得られた抽象構文木 AST を返します。ソースコード中にエラーがある場合、この関数は SyntaxError を発生させます。返り値は compiler.ast.Module インスタンスであり、この中に構文木が格納されています。
path で指定されたファイル中の Python ソースコードから得られた抽象構文木 AST を返します。これは parse(open(path).read()) と等価な働きをします。
ast に格納された抽象構文木の各ノードを先行順序 (pre-order) でたどっていきます。各ノードごとに visitor インスタンスの該当するメソッドが呼ばれます。
文字列 source 、Python モジュール、文あるいは式を exec 文あるいは eval() 関数で実行可能なバイトコードオブジェクトにコンパイルします。この関数は組み込みの compile() 関数を置き換えるものです。
filename は実行時のエラーメッセージに使用されます。
mode は、モジュールをコンパイルする場合は ‘exec’、 (対話的に実行される)単一の文をコンパイルする場合は ‘single’、式をコンパイルする場合には ‘eval’ を渡します。
引数 flags および dont_inherit は将来的に使用される文に影響しますが、いまのところはサポートされていません。
ファイル source をコンパイルし、.pyc ファイルを生成します。
compiler パッケージは以下のモジュールを含んでいます: ast, consts, future, misc, pyassem, pycodegen, symbols, transformer, そして visitor 。
compiler パッケージにはエラーチェックにいくつか問題が存在します。構文エラーはインタープリタの2つの別々のフェーズによって認識されます。ひとつはインタープリタのパーザによって認識されるもので、もうひとつはコンパイラによって認識されるものです。 compiler パッケージはインタープリタのパーザに依存しているので、最初の段階のエラーチェックは労せずして実現できています。しかしその次の段階は、実装されてはいますが、その実装は不完全です。たとえば compiler パッケージは引数に同じ名前が 2度以上出てきていてもエラーを出しません: def f(x, x): ...
compiler の将来のバージョンでは、これらの問題は修正される予定です。
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 によるものです。
compiler.ast モジュールは、各ノードのタイプとその要素を記述したテキストファイルからつくられます。各ノードのタイプはクラスとして表現され、そのクラスは抽象基底クラス compiler.ast.Node を継承し子ノードの名前属性を定義しています。
Node インスタンスはパーザジェネレータによって自動的に作成されます。ある特定の Node インスタンスに対する推奨されるインターフェイスとは、子ノードにアクセスするために public な (訳注: 公開された) 属性を使うことです。 public な属性は単一のノード、あるいは一連のノードのシーケンスに束縛されている (訳注: バインドされている) かもしれませんが、これは Node のタイプによって違います。たとえば Class ノードの bases 属性は基底クラスのノードのリストに束縛されており、 doc 属性は単一のノードのみに束縛されている、といった具合です。
各 Node インスタンスは lineno 属性をもっており、これは None かもしれません。 XXX どういったノードが使用可能な lineno をもっているかの規則は定かではない。
Node オブジェクトはすべて以下のメソッドをもっています:
子ノードと子オブジェクトを、これらが出てきた順で、平らなリスト形式にして返します。とくにノードの順序は、 Python 文法中に現れるものと同じになっています。すべての子が Node インスタンスなわけではありません。たとえば関数名やクラス名といったものは、ただの文字列として表されます。
子ノードをこれらが出てきた順で平らなリスト形式にして返します。このメソッドは 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 | ||
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 |
代入をあらわすのに使われる一群のノードが存在します。ソースコードにおけるそれぞれの代入文は、抽象構文木 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 などのようなリストとタプルの展開をあらわします。
代入対象ノードはまた、そのノードが代入で使われるのか、それとも del 文で使われるのかをあらわす属性 flags も持っています。 AssName は del x などのような del 文をあらわすのにも使われます。
ある式がいくつかの属性への参照をふくんでいるときは、代入あるいは del 文はただひとつだけの AssAttr ノードをもちます – 最終的な属性への参照としてです。それ以外の属性への参照は AssAttr インスタンスの expr 属性にある Getattr ノードによってあらわされます。
この節では、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("/tmp/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))))])
visitor パターンは… compiler パッケージは、Python のイントロスペクション機能を利用して visitor のために必要な大部分のインフラを省略した、visitor パターンの変種を使っています。
visit されるクラスは、visitor を受け入れるようにプログラムされている必要はありません。 visitor が必要なのはただそれがとくに興味あるクラスに対して visit メソッドを定義することだけです。それ以外はデフォルトの visit メソッドが処理します。
XXX visitor 用の魔法の visit() メソッド。
ASTVisitor は構文木を正しい順序でわたり歩くようにします。それぞれのノードはまず preorder() の呼び出しではじまります。各ノードに対して、これは ‘visitNodeType’ という名前のメソッドに対する preorder() 関数への visitor 引数をチェックします。ここで NodeType の部分はそのノードのクラス名です。たとえば While ノードなら、 visitWhile() が呼ばれるわけです。もしそのメソッドが存在している場合、それはそのノードを第一引数として呼び出されます。
ある特定のノード型に対する visitor メソッドでは、その子ノードをどのようにわたり歩くかが制御できます。 ASTVisitor は visitor に visit メソッドを追加することで、その visitor 引数を修正します。特定のノード型に対する visitor が存在しない場合、 default() メソッドが呼び出されます。
ASTVisitor オブジェクトには以下のようなメソッドがあります:
XXX 追加の引数を記述
バイトコード生成器はバイトコードを出力する visitor です。 visit メソッドが呼ばれるたびにこれは emit() メソッドを呼び出し、バイトコードを出力します。基本的なバイトコード生成器はモジュール、クラス、および関数によって拡張できます。アセンブラがこれらの出力された命令を低レベルのバイトコードに変換します。これはコードオブジェクトからなる定数のリスト生成や、分岐のオフセット計算といった処理をおこないます。