質問

関連 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 の動的な性質を活用しながらそれに取り組む方法はたくさんあると私は確信しています (例:動的ディスパッチング)。

マシンの「状態」に基づく「イベント」や「ディスパッチ」を受け取る「エンジン」の設計手法を追求しています。

役に立ちましたか?

解決

質問がよくわかりません。の デザインパターンはかなり明確です。を参照してください。 デザインパターンの本.

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

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

他のヒント

パイソン誌の2009年4月号では、私はpyparsingとimputilを使用して、Pythonの内国家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、レッド、イエロー、グリーン、クラス、および適切な状態遷移メソッドを作成します。コードは次のようなものを使用して、これらのクラスを呼び出すことができます:

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_machine のライブラリを書いたように、

私はまたstate_machinesのための現在のオプションと幸せではなかったです。

あなたは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.ロットの答えは、ステートマシンを実装するためのより良い方法だと思いますが、あなたはまだあなたの(state,event)ためのキーとしてdictを使用して、あなたのアプローチを継続したい場合は、より良いです。あなたのコードを変更:

class HandlerFsm(object):

  _fsm = {
    ("state_a","event"): "next_state",
    #...
  }

これはおそらく、あなたのステートマシンがどのように複雑に依存します。シンプルなステート・マシンの場合は、(NFAのための最先端のキーのリスト/セット/タプルへのDFAのための状態のキー、またはイベントキーにイベントキーの)dictsの辞書は、おそらく書き、理解するための最も簡単なものになります。

は、より複雑なステートマシンのために、私は宣言的状態をコンパイルすることができ、 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 __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}

私は間違いなく、このようなAよく知られたパターンを自分で実装することをお勧めしません。ただ、遷移のようなオープンソースの実装のために行くと、あなたならば、その周りに他のクラスをラップカスタム機能を必要としています。で I のこの記事、私は、この特定の実装とその特徴を好む理由を説明する。

私は、ツールが PySCXML のは、あまりにもよく見て必要だと思います。

このプロジェクトは、W3Cの定義を使用しています:ステートチャートXML(SCXML)の:ステートマシンコントロールの抽象化のための表記法

  

SCXMLはCCXMLとハレルの状態テーブルに基づいて、一般的なステートマシンベースの実行環境を提供します。

現在、SCXMLはワーキングドラフトです。しかし、チャンスは(それが9日のドラフトである)、それはすぐにW3C勧告を取得していることは非常に高いです。

ハイライトにもう一つの興味深い点は、ステートマシンを実行することが可能なJavaのSCXMLエンジンを作成し、維持することを目的にApache Commonsプロジェクトは、環境を抽象化しながら、インターフェース...

、SCXML文書を使用して定義があるということです

そして特定の他のツールのため、SCXMLはそのドラフトの状態を残しているときに、この技術をサポートすることは、将来的に出てくる...

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 倍になります。

最初に有限状態を必要とする問題は、多くの場合、発展してさらに多くの状態を必要とします。 番号, その時点で、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 __name__ == "__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.

私はここを "発言要求" を掲載しました。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top