pytho****@googl*****
pytho****@googl*****
2011年 11月 28日 (月) 12:06:41 JST
Revision: 58a5d016946a Author: Naoki INADA <inada****@klab*****> Date: Sun Nov 27 19:01:23 2011 Log: Update 2.7.2: library/parser http://code.google.com/p/python-doc-ja/source/detail?r=58a5d016946a Modified: /library/parser.rst ======================================= --- /library/parser.rst Sat Nov 27 10:59:46 2010 +++ /library/parser.rst Sun Nov 27 19:01:23 2011 @@ -285,22 +285,8 @@ ``st2tuple(ast, line_info)`` と同じ。 -.. _st-examples: - -例 --- - -.. index:: builtin: compile - -parser モジュールを使うと、バイトコード(:term:`bytecode`)が生成される前に -Python のソースコードの解析木に演算を行えるようになります。 -また、モジュールは情報発見のために解析木のインスペクションを提供していま す。 -例が二つあります。 -簡単な例では組み込み関数 :func:`compile` のエミュレーションを行っており、複 雑な例では情報を得るための解析木の使い方を示しています。 - - -:func:`compile` のエミュレーション -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +例: :func:`compile` のエミュレーション +---------------------------------------- たくさんの有用な演算を構文解析とバイトコード生成の間に行うことができます が、もっとも単純な演算は何もしないことです。 このため、 :mod:`parser` モジュールを使って中間データ構造を作ることは次の コードと等価です。 @@ -333,268 +319,3 @@ st = parser.expr(source_string) return st, st.compile() - -情報発見 -^^^^^^^^ - -.. index:: - single: string; documentation - single: docstrings - -あるアプリケーションでは解析木へ直接アクセスすることが役に立ちます。 -この節の残りでは、 :keyword:`import` を使って調査中のコードを実行中のイン タープリタにロードする必要も無しに、解析木を使って docstrings に定義されたモ ジュールのドキュメンテーションへのアクセスを可能にする方法を示します。 -これは信頼性のないコードを解析するためにとても役に立ちます。 - -一般に、例は興味のある情報を引き出すために解析木をどのような方法でたどれば よいかを示しています。 -二つの関数と一連のクラスが開発され、モジュールが提供する高レベルの関数とク ラスの定義をプログラムから利用できるようになります。 -クラスは情報を解析木から引き出し、便利な意味レベルでその情報へアクセスでき るようにします。 -一つの関数は単純な低レベルのパターンマッチング機能を提供し、もう一つの関数 は呼び出し側の代わりにファイル操作を行うという点でクラスへの高レベルなイン ターフェイスです。 -ここで言及されていて Python のインストールに必要ないすべてのソースファイル は、ディストリビューションの :file:`Demo/parser/` ディレクトリにあります。 - -Python の動的な性質によってプログラマは非常に大きな柔軟性を得ることができま す。 -しかし、クラス、関数およびメソッドを定義するときには、ほとんどのモジュール がこれの限られた部分しか必要としません。 -この例では、考察される定義だけがコンテキストのトップレベルにおいて定義され るものです。 -例を挙げると、モジュールのゼロ列目に :keyword:`def` 文によって定義される関 数で、 -:keyword:`if` ... :keyword:`else` コンストラクトの枝の中に定義されていない 関数(ある状況ではそうすることにもっともな理由があるのですが)。 -例で開発するコードによって、定義の入れ子を扱う予定です。 - - -より上位レベルの抽出メソッドを作るために知る必要があるのは、解析木構造がど のようなものかということと、それのどの程度まで関心を持つ必要があるのかという ことです。 -Python はやや深い解析木を使いますので、たくさんの中間ノードがあります。 -Python が使う形式文法を読んで理解することは重要です。 -これは配布物に含まれるファイル :file:`Grammar/Grammar` に明記されています。 -docstrings を探すときに対象として最も単純な場合について考えてみてください: -docstring の他に何も無いモジュール。 -(ファイル :file:`docstring.py` を参照してください。) :: - - """Some documentation. - """ - -インタープリタを使って解析木を調べると、数と括弧が途方に暮れるほど多くて、 ドキュメンテーションが入れ子になったタプルの深いところに埋まっていることがわ かります。 -:: - - >>> import parser - >>> import pprint - >>> st = parser.suite(open('docstring.py').read()) - >>> tup = st.totuple() - >>> pprint.pprint(tup) - (257, - (264, - (265, - (266, - (267, - (307, - (287, - (288, - (289, - (290, - (292, - (293, - (294, - (295, - (296, - (297, - (298, - (299, - (300, (3, '"""Some documentation.\n"""'))))))))))))))))), - (4, ''))), - (4, ''), - (0, '')) - -木の各ノードの最初の要素にある数はノード型です。 -それらは文法の終端記号と非終端記号に直接に対応します。 -残念なことに、それらは内部表現の整数で表されていて、生成された Python の構 造でもそのままになっています。 -しかし、 :mod:`symbol` と :mod:`token` モジュールはノード型の記号名と整数か らノード型の記号名へマッピングする辞書を提供します。 - -上に示した出力の中で、最も外側のタプルは四つの要素を含んでいます: -整数 ``257`` と三つの付加的なタプル。 -ノード型 ``257`` の記号名は :const:`file_input` です。 -これらの各内部タプルは最初の要素として整数を含んでいます。 -これらの整数 ``264`` と ``4`` 、 ``0`` は、ノード 型 :const:`stmt` 、 :const:`NEWLINE` 、 :const:`ENDMARKER` をそれぞれ表して います。 -これらの値はあなたが使っている Python のバージョンに応じて変化する可能性が あることに注意してください。 -マッピングの詳細については、 :file:`symbol.py` と :file:`token.py` を調べて ください。 -もっとも外側のノードがファイルの内容ではなく入力ソースに主に関係しているこ とはほとんど明らかで、差し当たり無視しても構いません。 -:const:`stmt` ノードはさらに興味深いです。 -特に、すべての docstrings は、このノードが作られるのとまったく同じように作 られ、違いがあるのは文字列自身だけである部分木にあります。 -同様の木の docstring と説明の対象である定義されたエンティティ(クラス、関数 あるいはモジュール)の関係は、前述の構造を定義している木の内部における docstring 部分木の位置によって与えられます。 - -実際の docstring を木の変数要素を意味する何かと置き換えることによって、簡単 なパターンマッチング方法で与えられたどんな部分木でも docstrings に対する一般 的なパターンと同等かどうかを調べられるようになります。 -例では情報の抽出の実例を示しているので、 ``['variable_name']`` という単純な 変数表現を念頭において、リスト形式ではなくタプル形式の木を安全に要求できま す。 -簡単な再帰関数でパターンマッチングを実装でき、その関数は真偽値と変数名から 値へのマッピングの辞書を返します。 -(ファイル :file:`example.py` を参照してください。) -:: - - from types import ListType, TupleType - - def match(pattern, data, vars=None): - if vars is None: - vars = {} - if type(pattern) is ListType: - vars[pattern[0]] = data - return 1, vars - if type(pattern) is not TupleType: - return (pattern == data), vars - if len(data) != len(pattern): - return 0, vars - for pattern, data in map(None, pattern, data): - same, vars = match(pattern, data, vars) - if not same: - break - return same, vars - -この構文の変数用の簡単な表現と記号のノード型を使うと、 -docstring 部分木の候補のパターンがとても読みやすくなります。 -(ファイル :file:`example.py` を参照してください。) -:: - - import symbol - import token - - DOCSTRING_STMT_PATTERN = ( - symbol.stmt, - (symbol.simple_stmt, - (symbol.small_stmt, - (symbol.expr_stmt, - (symbol.testlist, - (symbol.test, - (symbol.and_test, - (symbol.not_test, - (symbol.comparison, - (symbol.expr, - (symbol.xor_expr, - (symbol.and_expr, - (symbol.shift_expr, - (symbol.arith_expr, - (symbol.term, - (symbol.factor, - (symbol.power, - (symbol.atom, - (token.STRING, ['docstring']) - )))))))))))))))), - (token.NEWLINE, '') - )) - -このパターンと :func:`match` 関数を使うと、前に作った解析木からモジュールの -docstring を簡単に抽出できます:: - - >>> found, vars = match(DOCSTRING_STMT_PATTERN, tup[1]) - >>> found - 1 - >>> vars - {'docstring': '"""Some documentation.\n"""'} - -特定のデータを期待された位置から抽出できると、次は情報を期待できる場所はど こかという疑問に答える必要がでてきます。 -docstring を扱う場合、答えはとても簡単です: -docstring はコードブロック(:const:`file_input` または :const:`suite` ノード 型)の最初の :const:`stmt` ノードです。 -モジュールは一つの :const:`file_input` ノードと、正確にはそれぞれが一つ の :const:`suite` ノードを含むクラスと関数の定義で構成されます。 -クラスと関数は ``(stmt, (compound_stmt, (classdef, ...`` または ``(stmt, (compound_stmt, (funcdef, ...`` で始まるコードブロックノードの部分木として簡 単に識別されます。 -これらの部分木は :func:`match` によってマッチさせることができないことに注意 してください。 -なぜなら、数を無視して複数の兄弟ノードにマッチすることをサポートしていない からです。 -この限界を超えるためにより念入りにつくったマッチング関数を使うことができま すが、例としてはこれで充分です。 - -文が docstring かどうかを決定し、実際の文字列をその文から抽出する機能につい て考えると、ある作業にはモジュール全体の解析木を巡回してモジュールの各コンテ キストにおいて定義される名前についての情報を抽出し、その名前と docstrings を 結び付ける必要があります。 -この作業を行うコードは複雑ではありませんが、説明が必要です。 - -そのクラスへの公開インターフェイスは簡単で、おそらく幾分かより柔軟でしょ う。 -モジュールのそれぞれの "主要な" ブロックは、問い合わせのための幾つかのメソ ッドを提供するオブジェクトと、少なくともそれが表す完全な解析木の部分木を受け 取るコンストラクタによって記述されます。 -:class:`ModuleInfo` コンストラクタはオプションの *name* パラメータを受け取 ります。 -なぜなら、そうしないとモジュールの名前を決められないからです。 - -公開クラスには :class:`ClassInfo` 、 :class:`FunctionInfo` およ び :class:`ModuleInfo` が含まれます。 -すべてのオブジェクトはメソッ ド :meth:`get_name` 、 :meth:`get_docstring` 、 :meth:`get_class_names` およ び :meth:`get_class_info` を提供します。 -:class:`ClassInfo` オブジェクトは :meth:`get_method_names` と :meth:`get_method_info` をサポートしますが、他のクラス は :meth:`get_function_names` と :meth:`get_function_info` を提供していま す。 - -公開クラスが表すコードブロックの形式のそれぞれにおいて、トップレベルで定義 された関数が "メソッド" として参照されるという違いがクラスにはありますが、要 求される情報のほとんどは同じ形式をしていて、同じ方法でアクセスされます。 -クラスの外側で定義される関数との実際の意味の違いを名前の付け方が違うことで 反映しているため、実装はこの違いを保つ必要があります。 -そのため、公開クラスのほとんどの機能が共通の基底クラ ス :class:`SuiteInfoBase` に実装されており、他の場所で提供される関数とメソッ ドの情報に対するアクセサを持っています。 -関数とメソッドの情報を表すクラスが一つだけであることに注意してください。 -これは要素の両方の型を定義するために :keyword:`def` 文を使うことに似ていま す。 - -アクセサ関数のほとんどは :class:`SuiteInfoBase` で宣言されていて、サブクラ スでオーバーライドする必要はありません。 -より重要なこととしては、解析木からのほとんどの情報抽出 が :class:`SuiteInfoBase` コンストラクタに呼び出されるメソッドを通して行われ るということがあります。 -平行して形式文法を読めば、ほとんどのクラスのコード例は明らかです。 -しかし、再帰的に新しい情報オブジェクトを作るメソッドはもっと調査が必要で す。 -:file:`example.py` の :class:`SuiteInfoBase` 定義の関連する箇所を以下に示し ます:: - - class SuiteInfoBase: - _docstring = '' - _name = '' - - def __init__(self, tree = None): - self._class_info = {} - self._function_info = {} - if tree: - self._extract_info(tree) - - def _extract_info(self, tree): - # extract docstring - if len(tree) == 2: - found, vars = match(DOCSTRING_STMT_PATTERN[1], tree[1]) - else: - found, vars = match(DOCSTRING_STMT_PATTERN, tree[3]) - if found: - self._docstring = eval(vars['docstring']) - # discover inner definitions - for node in tree[1:]: - found, vars = match(COMPOUND_STMT_PATTERN, node) - if found: - cstmt = vars['compound'] - if cstmt[0] == symbol.funcdef: - name = cstmt[2][1] - self._function_info[name] = FunctionInfo(cstmt) - elif cstmt[0] == symbol.classdef: - name = cstmt[2][1] - self._class_info[name] = ClassInfo(cstmt) - -初期状態に初期化した後、コンストラクタは :meth:`_extract_info` メソッドを呼 び出します。 -このメソッドがこの例全体で行われる情報抽出の大部分を実行します。 -抽出には二つの別々の段階があります: -渡された解析木の docstring の位置の特定、解析木が表すコードブロック内の付加 的な定義の発見。 - -最初の :keyword:`if` テストは入れ子の suite が "短い形式" または "長い形 式" かどうかを決定します。 -以下のコードブロックの定義のように、コードブロックが同じ行であるときに短い 形式が使われます。 -:: - - def square(x): "Square an argument."; return x ** 2 - -長い形式では字下げされたブロックを使い、入れ子になった定義を許しています:: - - def make_power(exp): - "Make a function that raises an argument to the exponent `exp`." - def raiser(x, y=exp): - return x ** y - return raiser - -短い形式が使われるとき、コードブロックは docstring を最初 の :const:`small_stmt` 要素として(ことによるとそれだけを)持っています。 -このような docstring の抽出は少し異なり、より一般的な場合に使われる完全なパ ターンの一部だけを必要とします。 -実装されているように、 :const:`simple_stmt` ノードに :const:`small_stmt` ノードが一つだけある場合には、 docstring しかないことがあります。 -短い形式を使うほとんどの関数とメソッドが docstring を提供しないため、これで 充分だと考えられます。 -docstring の抽出は前述の :func:`match` 関数を使って進み、 -docstring が :class:`SuiteInfoBase` オブジェクトの属性として保存されます。 - -docstring を抽出した後、簡単な定義発見アルゴリズムを :const:`suite` ノード の :const:`stmt` ノードに対して実行します。 -短い形式の特別な場合はテストされません。 -短い形式では :const:`stmt` ノードが存在しないため、アルゴリズムは黙っ て :const:`simple_stmt` ノードを一つスキップします。 -正確に言えば、どんな入れ子になった定義も発見しません。 - -コードブロックのそれぞれの文をクラス定義(関数またはメソッドの定義、あるい は、何か他のもの)として分類します。 -定義文に対しては、定義された要素の名前が抽出され、コンストラクタに引数とし て渡される部分木の定義とともに定義に適した代理オブジェクトが作成されます。 -代理オブジェクトはインスタンス変数に保存され、適切なアクセサメソッドを使っ て名前から取り出されます。 - -公開クラスは :class:`SuiteInfoBase` クラスが提供するアクセサより具体的で、 必要とされるどんなアクセサでも提供します。 -しかし、実際の抽出アルゴリズムはコードブロックのすべての形式に対して共通の ままです。 -高レベルの関数をソースファイルから完全な情報のセットを抽出するために使うこ とができます。 -(ファイル :file:`example.py` を参照してください。) -:: - - def get_docs(fileName): - import os - import parser - - source = open(fileName).read() - basename = os.path.basename(os.path.splitext(fileName)[0]) - st = parser.suite(source) - return ModuleInfo(st.totuple(), basename) - -これはモジュールのドキュメンテーションに対する使いやすいインターフェイスで す。 -この例のコードで抽出されない情報が必要な場合は、機能を追加するための明確に 定義されたところで、コードを拡張することができます。