对于一个状态机,最基本的要素就是状态和事件,所以根据这个思路,我们可以设计一个具备基本功能的状态机。
以看碟片为例,DVD的状态包含:已开机,正在播放,正在暂停,已关机。而触发这些状态的事件有:遥控开机,遥控播放,遥控暂停,遥控关机。所以画一个状态转换表如下:
首先,设计状态基类。
class FsmState:
def enter(self, event, fsm): ## 参数event为触发状态转换的事件, fsm则为状态所在状态机
pass
def exit(self, fsm):
pass
FsmState将是我们这个状态机所有状态的基类。enter函数用于执行进入该状态的操作,exit函数用于执行离开该状态的操作。
然后我们将例子中的四个状态转换为代码:
class DvdPowerOn(FsmState):
def enter(self, event, fsm):
print("dvd is power on")
class DvdPlaying(FsmState):
def enter(self, event, fsm):
print("dvd is going to play")
def exit(self, fsm):
print("dvd stoped play")
class DvdPausing(FsmState):
def enter(self, event, fsm):
print("dvd is going to pause")
def exit(self, fsm):
print("dvd stopped pause")
class DvdPowerOff(FsmState):
def enter(self, event, fsm):
print("dvd is power off")
接下来,设计事件基类。
class FsmEvent:
pass
FsmEvent将是我们这个状态机中所有事件的基类。这个事件中的所有成员,均由子类自由添加。
子事件如下:
class PowerOnEvent(FsmEvent):
pass
class PowerOffEvent(FsmEvent):
pass
class PlayEvent(FsmEvent):
pass
class PauseEvent(FsmEvent):
pass
然后是我们的状态机基类,用于将我们的状态和事件联系起来。
from collections import namedtuple
Transaction = namedtuple("Transaction", ["prev_state", "event", "next_state"]) ## 一次状态转换的所有要素:上一个状态--事件-->下一个状态
class FSM:
def __init__(self, context): ## context:状态机上下文
self.context = context
self.state_transaction_table = [] ## 常规状态转换表
self.global_transaction_table = [] ## 全局状态转换表
self.current_state = None
self.working_state = FsmState
def add_global_transaction(self, event, end_state): # 全局转换,直接进入到结束状态
if not issubclass(end_state, FsmFinalState):
raise FsmException("The state should be FsmFinalState")
self.global_transaction_table.append(Transaction(self.working_state, event, end_state))
def add_transaction(self, prev_state, event, next_state):
if issubclass(prev_state, FsmFinalState):
raise FsmException("It's not allowed to add transaction after Final State Node")
self.state_transaction_table.append(Transaction(prev_state, event, next_state))
def process_event(self, event):
for transaction in self.global_transaction_table:
if isinstance(event, transaction.event):
self.current_state = transaction.next_state()
self.current_state.enter(event, self)
self.clear_transaction_table()
return
for transaction in self.state_transaction_table:
if isinstance(self.current_state, transaction.prev_state) and isinstance(event, transaction.event):
self.current_state.exit(self.context)
self.current_state = transaction.next_state()
self.current_state.enter(event, self)
if isinstance(self.current_state, FsmFinalState):
self.clear_transaction_table()
return
raise FsmException("Transaction not found")
def clear_transaction_table(self):
self.global_transaction_table = []
self.state_transaction_table = []
self.current_state = None
def run(self):
if len(self.state_transaction_table) == 0: return
self.current_state = self.state_transaction_table[0].prev_state()
self.current_state.enter(None, self)
def isRunning(self):
return self.current_state is not None
def next_state(self, event):
for transaction in self.global_transaction_table:
if isinstance(event, transaction.event):
return transaction.next_state
for transaction in self.state_transaction_table:
if isinstance(self.current_state, transaction.prev_state) and isinstance(event, transaction.event):
return transaction.next_state
return None
在上述代码中看到的FsmException定义如下:
class FsmException(Exception):
def __init__(self, description):
super().__init__(description)
所以最后,设计我们的DVD类。
class DVD(FSM):
def __init__(self):
super().__init__(None)
让DVD类运行起来:
dvd = DVD();
dvd.add_transaction(DvdPowerOff, PowerOnEvent, DvdPowerOn)
dvd.add_transaction(DvdPowerOn, PowerOffEvent, DvdPowerOff)
dvd.add_transaction(DvdPowerOn, PlayEvent, DvdPlaying)
dvd.add_transaction(DvdPlaying, PowerOffEvent, DvdPowerOff)
dvd.add_transaction(DvdPlaying, PauseEvent, DvdPausing)
dvd.add_transaction(DvdPausing, PowerOffEvent, DvdPowerOff)
dvd.add_transaction(DvdPausing, PlayEvent, DvdPlaying)
dvd.run()
dvd.process_event(PowerOnEvent())
dvd.process_event(PlayEvent())
dvd.process_event(PauseEvent())
dvd.process_event(PlayEvent())
dvd.process_event(PowerOffEvent())
运行结果如下:
dvd is power off
dvd is power on
dvd is going to play
dvd stoped play
dvd is going to pause
dvd stopped pause
dvd is going to play
dvd stoped play
dvd is power off