ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

state machineの話(あるいはPython3での実装)

Statechart
このような記事を見つけました。
ステートマシン(state machine)実装のための本があることを初めて知りました。

ここで提案されている手法は、[* 状態変数を使うかわりに現在の状態を示す関数を使う]というものである。たとえば「あ」という状態は`あ()`という関数で表現し、「い」という状態は`い()`という関数で表現する。「あ」や「い」という状態の上位階層として「あ行」という状態が存在するので、それは`あ行()`のような関数で表現する。このように、[* あらゆる状態を関数として表現する]ところがミソである。

iPhoneフリック入力はこんな感じらしいです。はぇ~。

そういえば以前にPython3でstate machineを実装したことがあるので、そのときのやり方をメモって置きます。結構悩んで実装した記憶が…。

from enum import Enum

class StateMachine():
    class State(Enum):
        """ state machineの状態 """
        Start = 0 # 状態0(初期状態)
        S1    = 1 # 状態1
        S2    = 2 # 状態2
        S3    = 3 # 状態3
        S4    = 5 # 状態4

    def __init__(self):
        self.state = self.State.Start

    def update_state(self):
        """ state machineを更新する(状態遷移する) """

        if self.state is self.State.Start:
            """ 初期状態 """
            self.state = self.State.S1
            print("State: Start")
            return

        elif self.state is self.State.S1:
            """ 状態1 """
            self.state = self.State.S2
            print("State: S1")
            return

        elif self.state is self.State.S2:
            """ 状態2 """
            self.state = self.State.S3
            print("State: S2")
            return

        elif self.state is self.State.S3:
            """ 状態3 """
            self.state = self.State.S4
            print("State: S3")
            return

        elif self.state is self.State.S4:
            """ 状態4 """
            self.state = self.State.S1
            print("State: S4")
            return

実際に使うときは、ループを回して使います。

import time

if __name__ == "__main__":
    sm = StateMachine()
    while True:
        sm.update_state()
        time.sleep(1.0)

上記の実装の良いところを挙げると、
・state machineの実装がクラス内で完全に閉じている
・使うときはupdate_state()を呼び出すだけ
・引数を増やしやすい
・シンプル
くらいです。まあ特別なことはしていません。

実際に処理を実装するとわかるのですが、処理が長くなるとif文が長くなり可読性落ちるので、実際の処理はメソッド化するのが無難です。

if文の数が多くなると可読性が落ちたり、無駄な評価がされて遅いというデメリットが考えられるので、if文をディクショナリに書き換えればもっと良くなるかもしれません。試してないですけど。

あと、これはstate machineを実装する上で一般的に言える注意点だと思うのですが、状態を変更した後は(self.stateを変更した後は)すぐにreturnしなければいけません。さもなければ状態を追いかけることが難しくなります。以前、状態が変更された後も処理が続き、条件によってはさらに状態が上書きされ、そしてまた条件によっては上書きされ…というのが続き、最後にreturnされるstate machineを見たことがあるのですが(そしてそのプログラムのバグを追いかけていたのですが)、恐ろしくしんどいです。リーダブルコード7.5章にも書いてありますが、できることなら関数から早く返しましょう。

関数で複数のreturn文を使ってはいけないと思っている人がいる。アホくさ。

ちなみにPythonには既にstate machineを扱うライブラリがあるようです。独自実装で特殊なことする必要がなければこれを使うという手もあります。便利世の中~。
transitionsでステートマシンを扱う [Python]