このStack Overflowの質問(Cステートマシン設計)に関連して、Stack Overflowの人々はあなたのPython私(およびコミュニティ)とのステートマシンデザインテクニック?
現時点では、以下に基づいたエンジンを使用しています。
class TrackInfoHandler(object):
def __init__(self):
self._state="begin"
self._acc=""
## ================================== Event callbacks
def startElement(self, name, attrs):
self._dispatch(("startElement", name, attrs))
def characters(self, ch):
self._acc+=ch
def endElement(self, name):
self._dispatch(("endElement", self._acc))
self._acc=""
## ===================================
def _missingState(self, _event):
raise HandlerException("missing state(%s)" % self._state)
def _dispatch(self, event):
methodName="st_"+self._state
getattr(self, methodName, self._missingState)(event)
## =================================== State related callbacks
しかし、Pythonの動的な性質(動的なディスパッチなど)を活用しながら、それに取り組む方法はたくさんあると確信しています。
私は、マシンの「状態」に基づいた「イベント」と「ディスパッチ」を受け取る「エンジン」の設計手法を求めています。
私は本当に質問をしません。 Stateデザインパターンはかなり明確です。 Design Patterns book を参照してください。
class SuperState( object ):
def someStatefulMethod( self ):
raise NotImplementedError()
def transitionRule( self, input ):
raise NotImplementedError()
class SomeState( SuperState ):
def someStatefulMethod( self ):
actually do something()
def transitionRule( self, input ):
return NextState()
これはかなり一般的な定型文で、Java、C++、Python(および他の言語も同様です)で使用されています。
状態遷移ルールがたまたま簡単な場合、遷移ルール自体をスーパークラスにプッシュするための最適化がいくつかあります。
前方参照が必要なので、クラスを名前で参照し、eval
を使用してクラス名を実際のクラスに変換することに注意してください。別の方法は、クラス変数の代わりに遷移ルールのインスタンス変数を作成し、すべてのクラスが定義された後にインスタンスを作成することです。
class State( object ):
def transitionRule( self, input ):
return eval(self.map[input])()
class S1( State ):
map = { "input": "S2", "other": "S3" }
pass # Overrides to state-specific methods
class S2( State ):
map = { "foo": "S1", "bar": "S2" }
class S3( State ):
map = { "quux": "S1" }
場合によっては、イベントはオブジェクトの等価性をテストするほど単純ではないため、より一般的な遷移規則は、関数とオブジェクトのペアの適切なリストを使用することです。
class State( object ):
def transitionRule( self, input ):
next_states = [ s for f,s in self.map if f(input) ]
assert len(next_states) >= 1, "faulty transition rule"
return eval(next_states[0])()
class S1( State ):
map = [ (lambda x: x == "input", "S2"), (lambda x: x == "other", "S3" ) ]
class S2( State ):
map = [ (lambda x: "bar" <= x <= "foo", "S3"), (lambda x: True, "S1") ]
ルールは順番に評価されるため、これにより「デフォルト」ルールが許可されます。
Python Magazineの2009年4月号で、pyparsingとimputilを使用して、PythonにState DSLを埋め込むことに関する記事を書きました。このコードにより、モジュールtrafficLight.pystateを記述できます。
# trafficLight.pystate
# define state machine
statemachine TrafficLight:
Red -> Green
Green -> Yellow
Yellow -> Red
# define some class level constants
Red.carsCanGo = False
Yellow.carsCanGo = True
Green.carsCanGo = True
Red.delay = wait(20)
Yellow.delay = wait(3)
Green.delay = wait(15)
dSLコンパイラーは、必要なすべてのTrafficLight、Red、Yellow、Greenクラス、および適切な状態遷移メソッドを作成します。コードは次のようなものを使用してこれらのクラスを呼び出すことができます。
import statemachine
import trafficLight
tl = trafficLight.Red()
for i in range(6):
print tl, "GO" if tl.carsCanGo else "STOP"
tl.delay()
tl = tl.next_state()
(残念ながら、imputilはPython 3.で削除されました。)
デコレータを使用してステートマシンを実装するための このデザインパターン があります。ページの説明から:
デコレータを使用して、クラスのイベントハンドラとなるメソッドを指定します。
ページにもサンプルコードがあります(非常に長いため、ここには貼り付けません)。
また、state_machinesの現在のオプションに満足できなかったため、 state_machine ライブラリを作成しました。
pip install state_machine
でインストールして、次のように使用できます。
@acts_as_state_machine
class Person():
name = 'Billy'
sleeping = State(initial=True)
running = State()
cleaning = State()
run = Event(from_states=sleeping, to_state=running)
cleanup = Event(from_states=running, to_state=cleaning)
sleep = Event(from_states=(running, cleaning), to_state=sleeping)
@before('sleep')
def do_one_thing(self):
print "{} is sleepy".format(self.name)
@before('sleep')
def do_another_thing(self):
print "{} is REALLY sleepy".format(self.name)
@after('sleep')
def snore(self):
print "Zzzzzzzzzzzz"
@after('sleep')
def big_snore(self):
print "Zzzzzzzzzzzzzzzzzzzzzz"
person = Person()
print person.current_state == person.sleeping # True
print person.is_sleeping # True
print person.is_running # False
person.run()
print person.is_running # True
person.sleep()
# Billy is sleepy
# Billy is REALLY sleepy
# Zzzzzzzzzzzz
# Zzzzzzzzzzzzzzzzzzzzzz
print person.is_sleeping # True
S. Lottの答えは、ステートマシンを実装するためのはるかに優れた方法だと思いますが、(state,event)
のキーとしてdict
の方が優れています。コードの変更:
class HandlerFsm(object):
_fsm = {
("state_a","event"): "next_state",
#...
}
このようなよく知られているパターンを自分で実装することは絶対にお勧めしません。 transitions のようなオープンソースの実装に行き、カスタム機能が必要な場合は別のクラスをラップします。 この投稿 で、この特定の実装とその機能を好む理由を説明します。
ツール PySCXML も詳しく調べる必要があると思います。
このプロジェクトでは、W3C定義を使用します。 State Chart XML(SCXML) :制御抽象化のための状態マシン表記法
SCXMLは、CCXMLおよびHarel State Tablesに基づいた一般的なステートマシンベースの実行環境を提供します
現在、SCXMLはワーキングドラフトです。しかし、すぐにW3C勧告を取得する可能性が非常に高くなります(9番目のドラフトです)。
ハイライトするもう1つの興味深い点は、環境インターフェースを抽象化しながら、SCXMLドキュメントを使用して定義されたステートマシンを実行できるJava SCXMLエンジンを作成および維持することを目的としたApache Commonsプロジェクトがあることです。 ..
また、他の特定のツールについては、SCXMLがドラフト状態を終了するときに、このテクノロジーのサポートが将来登場します...
おそらく、ステートマシンの複雑さに依存します。単純なステートマシンの場合、dictsの辞書(DFAの状態キーへのイベントキーの、またはNFAの状態キーのリスト/セット/タプルへのイベントキーの)は、おそらく最も簡単に記述して理解できるでしょう。
より複雑なステートマシンの場合、 [〜#〜] smc [〜#〜] について良いことを聞いたことがあります。これは、宣言型ステートマシン記述をPythonなどのさまざまな言語のコードにコンパイルできます。
次のコードは本当に簡単なソリューションです。唯一の興味深い部分は次のとおりです。
def next_state(self,cls):
self.__class__ = cls
各状態のすべてのロジックは、個別のクラスに含まれています。 「状態」は、実行中のインスタンスの「 __ class __ 」を置き換えることにより変更されます。
#!/usr/bin/env python
class State(object):
call = 0 # shared state variable
def next_state(self,cls):
print '-> %s' % (cls.__name__,),
self.__class__ = cls
def show_state(self,i):
print '%2d:%2d:%s' % (self.call,i,self.__class__.__name__),
class State1(State):
__call = 0 # state variable
def __call__(self,ok):
self.show_state(self.__call)
self.call += 1
self.__call += 1
# transition
if ok: self.next_state(State2)
print '' # force new line
class State2(State):
__call = 0
def __call__(self,ok):
self.show_state(self.__call)
self.call += 1
self.__call += 1
# transition
if ok: self.next_state(State3)
else: self.next_state(State1)
print '' # force new line
class State3(State):
__call = 0
def __call__(self,ok):
self.show_state(self.__call)
self.call += 1
self.__call += 1
# transition
if not ok: self.next_state(State2)
print '' # force new line
if __== '__main__':
sm = State1()
for v in [1,1,1,0,0,0,1,1,0,1,1,0,0,1,0,0,1,0,0]:
sm(v)
print '---------'
print vars(sm
結果:
0: 0:State1 -> State2
1: 0:State2 -> State3
2: 0:State3
3: 1:State3 -> State2
4: 1:State2 -> State1
5: 1:State1
6: 2:State1 -> State2
7: 2:State2 -> State3
8: 2:State3 -> State2
9: 3:State2 -> State3
10: 3:State3
11: 4:State3 -> State2
12: 4:State2 -> State1
13: 3:State1 -> State2
14: 5:State2 -> State1
15: 4:State1
16: 5:State1 -> State2
17: 6:State2 -> State1
18: 6:State1
---------
{'_State1__call': 7, 'call': 19, '_State3__call': 5, '_State2__call': 7}
XMLを処理するための有限状態マシンに手を伸ばすとは思わないでしょう。これを行う通常の方法は、スタックを使用することだと思います。
class TrackInfoHandler(object):
def __init__(self):
self._stack=[]
## ================================== Event callbacks
def startElement(self, name, attrs):
cls = self.elementClasses[name]
self._stack.append(cls(**attrs))
def characters(self, ch):
self._stack[-1].addCharacters(ch)
def endElement(self, name):
e = self._stack.pop()
e.close()
if self._stack:
self._stack[-1].addElement(e)
要素の種類ごとに、addCharacters
、addElement
、およびclose
メソッドをサポートするクラスが必要です。
編集:明確にするために、はい、私は通常、有限状態マシンは間違った答えであり、汎用プログラミング手法としてはゴミであり、離れておくべきだと主張するつもりです。
FSMがニースのソリューションである、非常によく理解され、明確に記述された問題がいくつかあります。たとえば、Lex
は良いものです。
そうは言っても、FSMは通常、変化にうまく対応しません。いつか、「要素Xをもう見たことがありますか?」という状態を少し追加したいとします。国旗。上記のコードでは、適切な要素クラスにブール属性を追加して完了です。有限状態マシンでは、状態と遷移の数を2倍にします。
最初に有限状態を必要とする問題は、numberのようにさらに多くの状態を要求するように進化することが多く、その時点でFSMスキームがトーストになるか、さらに悪いことに、あなたはそれをある種の一般化された状態機械に進化させ、その時点で本当に困っている。さらに進むと、ルールはコードのように機能し始めますが、デバッガーもツールもない、誰も知らない、あなたが発明した遅いインタープリター言語のコードです。
ここに私が思いついた「ステートフルオブジェクト」の解決策がありますが、状態の変更は比較的高価であるため、意図した目的にはかなり非効率的です。ただし、状態の変更頻度が低いオブジェクトや、状態の変更が制限された数だけのオブジェクトに対してはうまく機能する場合があります。利点は、状態が変更されると、冗長な間接化がないことです。
class T:
"""
Descendant of `object` that rectifies `__new__` overriding.
This class is intended to be listed as the last base class (just
before the implicit `object`). It is a part of a workaround for
* https://bugs.python.org/issue36827
"""
@staticmethod
def __new__(cls, *_args, **_kwargs):
return object.__new__(cls)
class Stateful:
"""
Abstract base class (or mixin) for "stateful" classes.
Subclasses must implement `InitState` mixin.
"""
@staticmethod
def __new__(cls, *args, **kwargs):
# XXX: see https://stackoverflow.com/a/9639512
class CurrentStateProxy(cls.InitState):
@staticmethod
def _set_state(state_cls=cls.InitState):
__class__.__bases__ = (state_cls,)
class Eigenclass(CurrentStateProxy, cls):
__new__ = None # just in case
return super(__class__, cls).__new__(Eigenclass, *args, **kwargs)
# XXX: see https://bugs.python.org/issue36827 for the reason for `T`.
class StatefulThing(Stateful, T):
class StateA:
"""First state mixin."""
def say_hello(self):
self._say("Hello!")
self.hello_count += 1
self._set_state(self.StateB)
return True
def say_goodbye(self):
self._say("Another goodbye?")
return False
class StateB:
"""Second state mixin."""
def say_hello(self):
self._say("Another hello?")
return False
def say_goodbye(self):
self._say("Goodbye!")
self.goodbye_count += 1
self._set_state(self.StateA)
return True
# This one is required by `Stateful`.
class InitState(StateA):
"""Third state mixin -- the initial state."""
def say_goodbye(self):
self._say("Why?")
return False
def __init__(self, name):
self.name = name
self.hello_count = self.goodbye_count = 0
def _say(self, message):
print("{}: {}".format(self.name, message))
def say_hello_followed_by_goodbye(self):
self.say_hello() and self.say_goodbye()
# ----------
# ## Demo ##
# ----------
if __== "__main__":
t1 = StatefulThing("t1")
t2 = StatefulThing("t2")
print("> t1, say hello.")
t1.say_hello()
print("> t2, say goodbye.")
t2.say_goodbye()
print("> t2, say hello.")
t2.say_hello()
print("> t1, say hello.")
t1.say_hello()
print("> t1, say hello followed by goodbye.")
t1.say_hello_followed_by_goodbye()
print("> t2, say goodbye.")
t2.say_goodbye()
print("> t2, say hello followed by goodbye.")
t2.say_hello_followed_by_goodbye()
print("> t1, say goodbye.")
t1.say_goodbye()
print("> t2, say hello.")
t2.say_hello()
print("---")
print( "t1 said {} hellos and {} goodbyes."
.format(t1.hello_count, t1.goodbye_count) )
print( "t2 said {} hellos and {} goodbyes."
.format(t2.hello_count, t2.goodbye_count) )
# Expected output:
#
# > t1, say hello.
# t1: Hello!
# > t2, say goodbye.
# t2: Why?
# > t2, say hello.
# t2: Hello!
# > t1, say hello.
# t1: Another hello?
# > t1, say hello followed by goodbye.
# t1: Another hello?
# > t2, say goodbye.
# t2: Goodbye!
# > t2, say hello followed by goodbye.
# t2: Hello!
# t2: Goodbye!
# > t1, say goodbye.
# t1: Goodbye!
# > t2, say hello.
# t2: Hello!
# ---
# t1 said 1 hellos and 1 goodbyes.
# t2 said 3 hellos and 2 goodbyes.
「発言のリクエスト」を投稿しました こちら 。
その他の関連プロジェクト:
ステートマシンをペイントして、コードで使用できます。