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 ノードになっています。

IfgetChildren() メソッドは、子ノードの平らなリストを返します。 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

代入対象の各ノードには代入されるオブジェクトの種類が記録されています。 AssNamea = 1 などの単純な変数名、 AssAttra.x = 1 などの属性に対する代入、 AssList および AssTuple はそれぞれ、 a, b, c = a_tuple などのようなリストとタプルの展開をあらわします。

代入対象ノードはまた、そのノードが代入で使われるのか、それとも削除文で使われるのかをあらわす属性 flags も持っています。 AssNamedel 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() メソッドを呼び出し、バイトコードを出力します。基本的なバイトコード生成器はモジュール、クラス、および関数のために特殊化されます。アセンブラがこれらの出力された命令を低レベルのバイトコードに変換します。これはコードオブジェクトからなる定数のリスト生成や、分岐のオフセット計算といった処理をおこないます。