32.1. parser --- Python 解析木にアクセスする


parser モジュールは Python の内部パーサとバイトコード・コンパイラへのインターフェイスを提供します。このインターフェイスの第一の目的は、 Python コードから Python の式の解析木を編集したり、これから実行可能なコードを作成したりできるようにすることです。これは任意の Python コードの断片を文字列として構文解析や変更を行うより良い方法です。なぜなら、構文解析がアプリケーションを作成するコードと同じ方法で実行されるからです。その上、高速です。

注釈

Python 2.5 以降、抽象構文木 (AST) の生成・コンパイルの段階に割り込むには ast モジュールを使うのがずっとお手軽です。

このモジュールについて注意すべきことが少しあります。それは作成したデータ構造を利用するために重要なことです。この文書は Python コードの解析木を編集するためのチュートリアルではありませんが、 parser モジュールを使った例をいくつか示しています。

もっとも重要なことは、内部パーサが処理する Python の文法についてよく理解しておく必要があるということです。言語の文法に関する完全な情報については、 Python 言語リファレンス を参照してください。標準の Python ディストリビューションに含まれるファイル Grammar/Grammar の中で定義されている文法仕様から、パーサ自身は作成されています。このモジュールが作成する ST オブジェクトの中に格納される解析木は、下で説明する expr() または suite() 関数によって作られるときに内部パーサから実際に出力されるものです。 sequence2st() が作る ST オブジェクトは忠実にこれらの構造をシミュレートしています。言語の形式文法が改訂されるために、 "正しい" と考えられるシーケンスの値が Python のあるバージョンから別のバージョンで変化することがあるということに注意してください。しかし、 Python のあるバージョンから別のバージョンへテキストのソースのままコードを移せば、目的のバージョンで正しい解析木を常に作成できます。ただし、インタープリタの古いバージョンへ移行する際に、最近の言語コンストラクトをサポートしていないことがあるという制限だけがあります。ソースコードが常に前方互換性があるのに対して、一般的に解析木はあるバージョンから別のバージョンへの互換性がありません。

st2list() または st2tuple() から返されるシーケンスのそれぞれの要素は単純な形式です。文法の非終端要素を表すシーケンスは常に一より大きい長さを持ちます。最初の要素は文法の生成規則を識別する整数です。これらの整数は C ヘッダファイル Include/graminit.h と Python モジュール symbol の中の特定のシンボル名です。シーケンスに付け加えられている各要素は、入力文字列の中で認識されたままの形で生成規則の構成要素を表しています: これらは常に親と同じ形式を持つシーケンスです。この構造の注意すべき重要な側面は、 if_stmt の中のキーワード if のような親ノードの型を識別するために使われるキーワードがいかなる特別な扱いもなくノードツリーに含まれているということです。例えば、 if キーワードはタプル (1, 'if') と表されます。ここで、 1 は、ユーザが定義した変数名と関数名を含むすべての NAME トークンに対応する数値です。行番号情報が必要なときに返される別の形式では、同じトークンが (1, 'if', 12) のように表されます。ここでは、 12 が終端記号の見つかった行番号を表しています。

終端要素は同じ方法で表現されますが、子の要素や識別されたソーステキストの追加は全くありません。上記の if キーワードの例が代表的なものです。終端記号のいろいろな型は、 C ヘッダファイル Include/token.h と Python モジュール token で定義されています。

ST オブジェクトはこのモジュールの機能をサポートするために必要ありませんが、三つの目的から提供されています: アプリケーションが複雑な解析木を処理するコストを償却するため、Python のリストやタプル表現に比べてメモリ空間を保全する解析木表現を提供するため、解析木を操作する追加モジュールを C で作ることを簡単にするため。ST オブジェクトを使っていることを隠すために、簡単な "ラッパー" クラスを Python で作ることができます。

parser モジュールは二、三の別々の目的のために関数を定義しています。もっとも重要な目的は ST オブジェクトを作ることと、 ST オブジェクトを解析木とコンパイルされたコードオブジェクトのような他の表現に変換することです。しかし、 ST オブジェクトで表現された解析木の型を調べるために役に立つ関数もあります。

参考

symbol モジュール
解析木の内部ノードを表す便利な定数。
token モジュール
便利な解析木の葉のノードを表す定数とノード値をテストするための関数。

32.1.1. ST オブジェクトを作成する

ST オブジェクトはソースコードあるいは解析木から作られます。ST オブジェクトをソースから作るときは、'eval''exec' 形式を作成するために別々の関数が使われます。

parser.expr(source)

まるで compile(source, 'file.py', 'eval') への入力であるかのように、 expr() 関数はパラメータ source を構文解析します。解析が成功した場合は、 ST オブジェクトは内部解析木表現を保持するために作成されます。そうでなければ、適切な例外を発生させます。

parser.suite(source)

まるで compile(source, 'file.py', 'exec') への入力であるかのように、 suite() 関数はパラメータ source を構文解析します。解析が成功した場合は、 ST オブジェクトは内部解析木表現を保持するために作成されます。そうでなければ、適切な例外を発生させます。

parser.sequence2st(sequence)

この関数はシーケンスとして表現された解析木を受け取り、可能ならば内部表現を作ります。木が Python の文法に合っていることと、すべてのノードが Python のホストバージョンで有効なノード型であることを確認した場合は、 ST オブジェクトが内部表現から作成されて呼び出し側へ返されます。内部表現の作成に問題があるならば、あるいは木が正しいと確認できないならば、 ParserError 例外を発生します。この方法で作られた ST オブジェクトが正しくコンパイルできると決めつけない方がよいでしょう。 ST オブジェクトが compilest() へ渡されたとき、コンパイルによって送出された通常の例外がまだ発生するかもしれません。これは(MemoryError 例外のような)構文に関係していない問題を示すのかもしれないし、 del f(0) を解析した結果のようなコンストラクトが原因であるかもしれません。このようなコンストラクトは Python のパーサを逃れますが、バイトコードインタープリタによってチェックされます。

終端トークンを表すシーケンスは、(1, 'name') 形式の二つの要素のリストか、または (1, 'name', 56) 形式の三つの要素のリストです。三番目の要素が存在する場合は、有効な行番号だとみなされます。行番号が指定されるのは、入力木の終端記号の一部に対してです。

parser.tuple2st(sequence)

これは sequence2st() と同じ関数です。このエントリポイントは後方互換性のために維持されています。

32.1.2. ST オブジェクトを変換する

作成するために使われた入力に関係なく、ST オブジェクトはリスト木またはタプル木として表される解析木へ変換されるか、または実行可能なオブジェクトへコンパイルされます。解析木は行番号情報を持って、あるいは持たずに抽出されます。

parser.st2list(st, line_info=False, col_info=False)

この関数は呼び出し側から st に ST オブジェクトを受け取り、解析木と等価な Python のリストを返します。結果のリスト表現はインスペクションあるいはリスト形式の新しい解析木の作成に使うことができます。リスト表現を作るためにメモリが利用できる限り、この関数は失敗しません。解析木がインスペクションのためだけにつかわれるならば、メモリの消費量と断片化を減らすために st2tuple() を代わりに使うべきです。リスト表現が必要とされるとき、この関数はタプル表現を取り出して入れ子のリストに変換するよりかなり高速です。

line_info が真ならば、トークンを表すリストの三番目の要素として行番号情報がすべての終端トークンに含まれます。与えられた行番号はトークン が終わる 行を指定していることに注意してください。フラグが偽または省略された場合は、この情報は省かれます。

parser.st2tuple(st, line_info=False, col_info=False)

この関数は呼び出し側から st に ST オブジェクトを受け取り、解析木と等価な Python のタプルを返します。リストの代わりにタプルを返す以外は、この関数は st2list() と同じです。

line_info が真ならば、トークンを表すリストの三番目の要素として行番号情報がすべての終端トークンに含まれます。フラグが偽または省略された場合は、この情報は省かれます。

parser.compilest(st, filename='<syntax-tree>')

組み込みの exec()eval() 関数への呼び出しとして使えるコードオブジェクトを生成するために、 Python バイトコードコンパイラを ST オブジェクトに対して呼び出すことができます。この関数はコンパイラへのインターフェイスを提供し、 filename パラメータで指定されるソースファイル名を使って、 st からパーサへ内部解析木を渡します。 filename に与えられるデフォルト値は、ソースが ST オブジェクトだったことを示唆しています。

ST オブジェクトをコンパイルすることは、コンパイルに関する例外を引き起こすことになるかもしれません。例としては、 del f(0) の解析木によって発生させられる SyntaxError があります: この文は Python の形式文法としては正しいと考えられますが、正しい言語コンストラクトではありません。この状況に対して発生する SyntaxError は、実際には Python バイトコンパイラによって通常作り出されます。これが parser モジュールがこの時点で例外を発生できる理由です。解析木のインスペクションを行うことで、コンパイルが失敗するほとんどの原因をプログラムによって診断することができます。

32.1.3. ST オブジェクトに対する問い合わせ

ST が式または suite として作成されたかどうかをアプリケーションが決定できるようにする二つの関数が提供されています。これらの関数のどちらも、 ST が expr() または suite() を通してソースコードから作られたかどうか、あるいは、 sequence2st() を通して解析木から作られたかどうかを決定できません。

parser.isexpr(st)

st'eval' 形式を表している場合に、この関数は真を返します。そうでなければ、偽を返します。これは役に立ちます。なぜならば、通常は既存の組み込み関数を使ってもコードオブジェクトに対してこの情報を問い合わせることができないからです。このどちらのようにも compilest() によって作成されたコードオブジェクトに問い合わせることはできませんし、そのコードオブジェクトは組み込み compile() 関数によって作成されたコードオブジェクトと同じであることに注意してください。

parser.issuite(st)

ST オブジェクトが(通常 "suite" として知られる) 'exec' 形式を表しているかどうかを報告するという点で、この関数は isexpr() に酷似しています。追加の構文が将来サポートされるかもしれないので、この関数が not isexpr(st) と等価であるとみなすのは安全ではありません。

32.1.4. 例外とエラー処理

parser モジュールは例外を一つ定義していますが、Python ランタイム環境の他の部分が提供する別の組み込み例外を発生させることもあります。各関数が発生させる例外の情報については、それぞれ関数を参照してください。

exception parser.ParserError

parser モジュール内部で障害が起きたときに発生する例外。普通の構文解析中に発生する組み込みの SyntaxError ではなく、一般的に妥当性確認が失敗した場合に引き起こされます。例外の引数としては、障害の理由を説明する文字列である場合と、 sequence2st() へ渡される解析木の中の障害を引き起こすシーケンスを含むタプルと説明用の文字列である場合があります。モジュール内の他の関数の呼び出しは単純な文字列値を検出すればよいだけですが、 sequence2st() の呼び出しはどちらの例外の型も処理できる必要があります。

普通は構文解析とコンパイル処理によって発生する例外を、関数 compilest()expr() および suite() が発生させることに注意してください。このような例外には組み込み例外 MemoryErrorOverflowErrorSyntaxError および SystemError が含まれます。こうした場合には、これらの例外が通常その例外に関係する全ての意味を伝えます。詳細については、各関数の説明を参照してください。

32.1.5. ST オブジェクト

ST オブジェクト間の順序と等値性の比較がサポートされています。 (pickle モジュールを使った) ST オブジェクトのピクルス化もサポートされています。

parser.STType

expr()suite()sequence2st() が返すオブジェクトの型。

ST オブジェクトは次のメソッドを持っています:

ST.compile(filename='<syntax-tree>')

compilest(st, filename) と同じ。

ST.isexpr()

isexpr(st) と同じ。

ST.issuite()

issuite(st) と同じ。

ST.tolist(line_info=False, col_info=False)

st2list(st, line_info, col_info) と同じ。

ST.totuple(line_info=False, col_info=False)

st2tuple(st, line_info, col_info) と同じ。

32.1.6. 例: compile() のエミュレーション

たくさんの有用な演算を構文解析とバイトコード生成の間に行うことができますが、もっとも単純な演算は何もしないことです。このため、 parser モジュールを使って中間データ構造を作ることは次のコードと等価です

>>> code = compile('a + 5', 'file.py', 'eval')
>>> a = 5
>>> eval(code)
10

parser モジュールを使った等価な演算はやや長くなりますが、 ST オブジェクトとして中間内部解析木が維持されるようにします:

>>> import parser
>>> st = parser.expr('a + 5')
>>> code = st.compile('file.py')
>>> a = 5
>>> eval(code)
10

ST とコードオブジェクトの両方が必要なアプリケーションでは、このコードを簡単に利用できる関数にまとめることができます:

import parser

def load_suite(source_string):
    st = parser.suite(source_string)
    return st, st.compile()

def load_expression(source_string):
    st = parser.expr(source_string)
    return st, st.compile()