50

このスタック オーバーフローの質問 (C ステート マシンの設計)に関連して、スタック オーバーフローの皆さん、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 の動的な性質 (動的ディスパッチなど) を活用しながら、それを行う方法はたくさんあると確信しています。

マシンの「状態」から「イベント」を受けて「発信」する「エンジン」の設計手法を追求しています。

4

12 に答える 12

40

質問がよくわかりません。State Design パターンは非常に明確です。デザイン パターン ブックを参照してください。

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") ]

ルールは順番に評価されるため、これにより「デフォルト」ルールが許可されます。

于 2010-01-20T14:27:44.677 に答える
12

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 で削除されました。)

于 2010-01-20T17:19:18.523 に答える
9

デコレータを使用してステート マシンを実装するためのこの設計パターンがあります。ページの説明から:

デコレーターは、どのメソッドがクラスのイベント ハンドラーであるかを指定するために使用されます。

ページにはサンプルコードもあります (かなり長いのでここには貼り付けません)。

于 2010-01-20T14:51:51.560 に答える
5

また、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
于 2014-03-13T18:48:51.913 に答える
3

S.ロットの答えはステートマシンを実装するためのはるかに優れた方法だと思いますが、それでもアプローチを続けたい場合は(state,event)、キーとして使用するdict方が良いでしょう。コードの変更:

class HandlerFsm(object):

  _fsm = {
    ("state_a","event"): "next_state",
    #...
  }
于 2010-01-20T14:53:30.043 に答える
2

ツールPySCXMLも詳しく調べる必要があると思います。

このプロジェクトでは、W3C の定義であるState Chart XML (SCXML) : State Machine Notation for Control Abstraction を使用しています。

SCXML は、CCXML および Harel ステート テーブルに基づいて、一般的なステート マシン ベースの実行環境を提供します。

現在、SCXML はワーキング ドラフトです。しかし、すぐに W3C の推奨を受ける可能性が非常に高いです (これは 9 番目のドラフトです)。

注目すべきもう 1 つの興味深い点は、環境インターフェイスを抽象化しながら、SCXML ドキュメントを使用して定義されたステート マシンを実行できる Java SCXML エンジンを作成および維持することを目的とした Apache Commons プロジェクトがあることです...

また、他の特定のツールについては、SCXML がドラフト ステータスを離れたときに、このテクノロジをサポートする予定です...

于 2011-09-19T12:07:36.307 に答える
2

おそらく、ステート マシンの複雑さに依存します。単純なステート マシンの場合、dict の dict (DFA のイベント キーから状態キーへの、または NFA の状態キーのリスト/セット/タプルへのイベント キーの) は、おそらく最も簡単に記述および理解できます。

より複雑なステート マシンについては、宣言型のステート マシンの記述をコンパイルして、Python を含むさまざまな言語でコード化できるSMCについて良いことを聞いたことがあります。

于 2010-01-20T14:45:07.240 に答える
2

次のコードは、非常に単純なソリューションです。唯一の興味深い部分は次のとおりです。

   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 __name__ == '__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}
于 2011-05-28T01:18:25.007 に答える
1

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のようなさらに多くの状態を必要とするように進化することが非常によくあります。本当に困ります。先に進むほど、ルールはコードのように機能し始めますが、コードはあなたが発明した遅いインタープリター言語であり、他の誰も知らないため、デバッガーもツールもありません。

于 2010-01-20T15:24:44.513 に答える
0

その他の関連プロジェクト:

ステートマシンをペイントして、コードで使用できます。

于 2013-06-05T09:16:52.573 に答える