web-dev-qa-db-ja.com

Pythonステートマシン設計

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

私は、マシンの「状態」に基づいた「イベント」と「ディスパッチ」を受け取る「エンジン」の設計手法を求めています。

45
jldupont

私は本当に質問をしません。 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") ]

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

39
S.Lott

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

12
PaulMcG

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

デコレータを使用して、クラスのイベントハンドラとなるメソッドを指定します。

ページにもサンプルコードがあります(非常に長いため、ここには貼り付けません)。

8
Trent

また、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
5
Jonathan

S. Lottの答えは、ステートマシンを実装するためのはるかに優れた方法だと思いますが、(state,event)のキーとしてdictの方が優れています。コードの変更:

class HandlerFsm(object):

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

このようなよく知られているパターンを自分で実装することは絶対にお勧めしません。 transitions のようなオープンソースの実装に行き、カスタム機能が必要な場合は別のクラスをラップします。 この投稿 で、この特定の実装とその機能を好む理由を説明します。

2
Iwan LD

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

このプロジェクトでは、W3C定義を使用します。 State Chart XML(SCXML) :制御抽象化のための状態マシン表記法

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

現在、SCXMLはワーキングドラフトです。しかし、すぐにW3C勧告を取得する可能性が非常に高くなります(9番目のドラフトです)。

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

また、他の特定のツールについては、SCXMLがドラフト状態を終了するときに、このテクノロジーのサポートが将来登場します...

2
gecco

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

より複雑なステートマシンの場合、 [〜#〜] smc [〜#〜] について良いことを聞いたことがあります。これは、宣言型ステートマシン記述をPythonなどのさまざまな言語のコードにコンパイルできます。

2
Ben Karel

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

   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}
2
cmcginty

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)

要素の種類ごとに、addCharactersaddElement、およびcloseメソッドをサポートするクラスが必要です。

編集:明確にするために、はい、私は通常、有限状態マシンは間違った答えであり、汎用プログラミング手法としてはゴミであり、離れておくべきだと主張するつもりです。

FSMがニースのソリューションである、非常によく理解され、明確に記述された問題がいくつかあります。たとえば、Lexは良いものです。

そうは言っても、FSMは通常、変化にうまく対応しません。いつか、「要素Xをもう見たことがありますか?」という状態を少し追加したいとします。国旗。上記のコードでは、適切な要素クラスにブール属性を追加して完了です。有限状態マシンでは、状態と遷移の数を2倍にします。

最初に有限状態を必要とする問題は、numberのようにさらに多くの状態を要求するように進化することが多く、その時点でFSMスキームがトーストになるか、さらに悪いことに、あなたはそれをある種の一般化された状態機械に進化させ、その時点で本当に困っている。さらに進むと、ルールはコードのように機能し始めますが、デバッガーもツールもない、誰も知らない、あなたが発明した遅いインタープリター言語のコードです。

1
Jason Orendorff

ここに私が思いついた「ステートフルオブジェクト」の解決策がありますが、状態の変更は比較的高価であるため、意図した目的にはかなり非効率的です。ただし、状態の変更頻度が低いオブジェクトや、状態の変更が制限された数だけのオブジェクトに対してはうまく機能する場合があります。利点は、状態が変更されると、冗長な間接化がないことです。

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.

「発言のリクエスト」を投稿しました こちら

0
Alexey

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

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

0
Veselin Penev