jldupont jldupont - 1 year ago 189
Python Question

Python state-machine design

Related to this SO question (C state-machine design), could you SO folks share with me (and the community!) your Python state-machine design techniques?

Update3: At the moment, I am going for an engine based on the following:

class TrackInfoHandler(object):
def __init__(self):

## ================================== Event callbacks

def startElement(self, name, attrs):
self._dispatch(("startElement", name, attrs))

def characters(self, ch):

def endElement(self, name):
self._dispatch(("endElement", self._acc))

## ===================================
def _missingState(self, _event):
raise HandlerException("missing state(%s)" % self._state)

def _dispatch(self, event):
getattr(self, methodName, self._missingState)(event)

## =================================== State related callbacks

But I am sure there are tons of ways of going at it whilst leveraging Python's dynamic nature (e.g. dynamic dispatching).

Update2: I am after design techniques for the "engine" that receives the "events" and "dispatches" against those based on the "state" of the machine.

Answer Source

I don't really get the question. The State Design pattern is pretty clear. See the 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()

That's pretty common boilerplate, used in Java, C++, Python (and I'm sure other languages, also).

If your state transition rules happen to be trivial, there are some optimizations to push the transition rule itself into the superclass.

Note that we need to have forward references, so we refer to classes by name, and use eval to translate a class name to an actual class. The alternative is to make the transition rules instance variables instead of class variables and then create the instances after all the classes are defined.

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" }

In some cases, your event isn't as simple as testing objects for equality, so a more general transition rule is to use a proper list of function-object pairs.

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

Since the rules are evaluated sequentially, this allows a "default" rule.