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