Python 状态机设计
关于这个Stack Overflow的问题(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的动态特性来实现,比如动态调度。
我想要的是一种设计技巧,用于“引擎”,它能够接收“事件”,并根据机器的“状态”进行相应的“调度”。
12 个回答
这里有一个关于使用装饰器来实现状态机的设计模式,具体可以参考这个链接。页面上是这样描述的:
装饰器用于指定哪些方法是这个类的事件处理器。
页面上还有示例代码,不过因为代码比较长,我就不在这里贴出来了。
在2009年4月的《Python Magazine》杂志上,我写了一篇关于如何在Python中嵌入状态DSL(领域特定语言)的文章,使用了pyparsing和imputil这两个工具。这个代码可以让你编写一个叫做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中已经被去掉了。)
我不太明白这个问题。状态设计模式其实挺简单的。可以看看这本设计模式的书。
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") ]
因为这些规则是按顺序评估的,所以可以设置一个“默认”规则。