モジュール docutils.statemachine
¶
個人的に Docutils でもっとも興味のある機能が本節で見ていくクラス
StateMachine
およびそれに付随する一連の関係機能だ。 Docutils は
reStructuredText というテキスト形式を処理するために存在するパッケージであり、この状態機械はまさにその処理の主役級の機能なのだ。実装を調べていくと、このモジュールの構造は上層と下層の二段構えになっている。下層は reStructuredText のテキスト解析に特化したマシーンで、上層がより抽象的なテキスト解析の手続きの枠組みを提供する体になっている。
私の目的はこの上層部を reStructuredText ではなく、もっと別のテキスト形式、例えば趣味の麻雀ゲームの牌譜や、ダンジョンマスター (RTC) のスクリプトの解析に応用できないかと考えている。
本節では最初に関連するクラス図を示す。それから抽象クラス、上層部の機能をおおまかに理解し易さ重視で見ていく。次にいくつかのテキスト解析例を示し、最後に本モジュールに関する感想を述べる。
クラス図¶
クラス StateMachine
および State
から派生したクラス群を大雑把に表現した図を示す。他のページの図と同様に、正確さよりも理解し易さを重視して若干の省略とウソが入っている。
図内に示されているクラスは次のどちらかにある。
モジュール
statemachine
モジュール
parsers.rst.states
GoF デザインパターンでいうところの State パターン、それに Observer パターンが採用されている。本節で見たいのは前者の詳細。
コード読解¶
モジュール statemachine
の docstring にかなり詳細な説明文が記されている。興味のある部分だけかいつまんで記す。
上層クラス概要¶
クラス
StateMachine
とState
がそれぞれ状態機械と状態を抽象的に表現する。クラス
StateMachineWS
とStateWS
のように末尾がWS
のものは、処理テキストの空白文字を「気にする」版。クラス
SearchStateMachine
のように、先頭がSearch
から始まるものは、テキストの正規表現による検索をre.search
により実現する版。通常版はre.match
による。
処理対象のテキストの特性に応じて、空白と検索のスタイルをクラスの選択で決めろということだろう。
モジュール statemachine
の利用法¶
クライアントコードの骨格はこうなる。まずは WS
でも Search
でもない版を説明する。
from statemachine import StateMachine, State, string2lines
import re
# Derive subclasses of State for your state machine...
class InitialState(State):
patterns = {'atransition': r'pattern', ...}
initial_transitions = ['atransition', ...]
def atransition(self, match, context, next_state):
# do something
result = [...]
return context, next_state, result
# More methods for transitions...
# More subclasses...
sm = StateMachine(state_classes=[InitialState, ...],
initial_state='InitialState')
with open('inputfile') as input:
input_string = input.read()
input_lines = string2lines(input_string)
results = sm.run(input_lines)
sm.unlink()
各種スーパークラスおよび補助関数を
import
する。また、状態クラスを定義するのであれば間違いなく Python 標準のモジュールre
も要る。状態遷移図に基づいて、各状態に対応する
State
のサブクラスを定義する。クラスデータ
patterns
はメソッド名と正規表現の辞書だ。ここのある正規表現と状態機械が今見ている文字列とマッチしたときに、キーとなるメソッドが呼び出される仕組みだ。クラスデータ
initial_transitions
はメソッド名のリストである。ここは正確に理解していないが、実行時に機械から呼び出されるメソッドはここにも格納されている必要がある。この「正規表現と関連付けられたメソッド」の実装が本質的にはテキスト処理をする。
match
: 正規表現オブジェクトのmatch
呼び出し結果オブジェクトcontext
: アプリケーション依存のデータnext_state
: 状態遷移図におけるこの状態の次の状態の名前(文字列)ある状態から次の状態へ遷移することを指示するには、メソッドの戻り値の
next_state
に状態クラス名を文字列で指定する。例えば次の状態をクラスSecondState
にしたいのならば次のようにする。こうすることで、次に呼びだされるメソッドがクラスSecondState
のリストinitial_transitions
のどれかが示すメソッドになるはず。def atransition(self, match, context, next_state): # do something result = [...] if some_condition: next_state = 'SecondState' return context, next_state, result
StateMachine
オブジェクトを直接生成する。引数
state_classes
には状態機械の取り得る状態遷移を列挙して指定する。動作効率を配慮すれば、先に遷移先になりそうな状態名というかクラスを、リストのより先頭に指定するのが良いように思える。通常は動作さえすれば良いので、好きな順にクラスを書いて良い。引数
initial_state
には状態遷移の初期状態に相当するクラス名を文字列として指定する。
補助関数
string2lines
はこれはオマケのようなもの。StateMachine
系は元の(巨大な)文字列を行ごとに分割したものを処理することになる。そこで、ファイルの内容を改行文字でsplit
してリスト化したオブジェクトが要る。このためにこの補助関数を使うに過ぎない。メソッド
run
を呼び出すことで状態遷移が始まる。この説明コードでは結果がresults
に出力されるように読めるが、引数context
を明示して、状態オブジェクトに操作させてそれを出力とする方法がある。最後の
unlink
呼び出しは一種のクリナップメソッド。書き忘れたとしても何ということはない。
クラス State
¶
前項で述べたこと以外を記す。
メンバーデータ
self.state_machine
で状態機械オブジェクトにアクセスする。どの
self.transitions
の遷移メソッドの正規表現パターンにもマッチしないテキストが現れた時に、メンバーメソッドno_match
が呼び出される。エラー処理に使えるだろう。メソッド
bof
およびeof
は入力「ファイル」の先頭および末尾の到達時に呼び出される。これもオーバーライドしない限りは何もしない。メソッド
nop
は何もしない遷移メソッド。単に状態を遷移させるためだけに有用。
入れ子状態機械¶
話がややこしくなるが、状態機械の中に状態機械を入れることができる。
State.nested_sm
とState.nested_sm_kwargs
を適宜上書きする。前者にはStateMachine
またはそのサブクラスの型を、後者には そのコンストラクターに渡す引数をdict
オブジェクトをそれぞれ指定する。例:クラス
RSTState
の実装WS
系で実装する場合は、さらに次のものの上書きも適宜行う。indent_sm
indent_sm_kwargs
known_indent_sm
known_indent_sm_kwargs
indent()
known_indent()
first_known_indent()
例:不明
応用¶
クラス
RSTStateMachine
およびRSTState
さらにその膨大なサブクラス群。某麻雀ゲームの牌譜を処理するスクリプトを
docutils.statemachine
を再利用することで実装できた。別リポジトリーのファイルだが、アドレスを次に示す。<https://github.com/showa-yojyo/bin/blob/master/mjstat/states.py>
牌譜となるテキストデータのサンプルもこのパスの近所にあるので、スクリプトとテキストを交互に観察して、サブクラスの作り方の要領をつかめると思う。ただし、私の状態クラスの設計があまり良くない。おそらく WS 系を用いれば、テキストがインデント指向になっていることを上手く利用できるハズ。
あとは Dungeon Master (RTC) のスクリプトの解析スクリプトを作りたいという考えがある。こちらはスクリプトというか、どちらかというと ini ファイルのような風味があるテキスト。
感想¶
モジュール
statemachine
上層部の機能は reStructuredText 無関係にエンドユーザー(私)が利用できる。型を取り扱うのに二つのマナーがあることに注意。すなわちクラスを直接指示するものと、クラス名を文字列で指示するものだ。コードの位置がクライアント方面に近づくほど、文字列での指定が多くなりがちなのか。
入れ子処理を試したい。つまり
State.nested_sm
をオーバーライドするサンプルを書きたい。どうも処理テキストにあるインデントが意味を持つような場合に有用らしい。