This is a state machine template. It can be used as a state machine, or a state machine tester. There is very little setup required and all the states are added based on the state docstring. I made this after a friend came to me asking how I'd implement one, complaining that he couldn't find a nice example. I hope this helps.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | '''
-=nero
Monday, May 08, 2011
'''
import sys
import re
import pprint
class StateMachineFoo(object):
'''
This is a template for a finite state machine that is dynamically built based
on the function names and docstring for the given functions
'''
def __init__(self):
self.init_sm()
self.state_trace = []
def init_sm(self):
# find all the states based on the regex
m = re.compile(r'_state.*',re.I)
# return a list of matches
state_names = filter(m.search,dir(self))
# get the function pointers to all of our states
state_ptrs = [getattr(self,state) for state in state_names]
# from the function pointers, suck up our dockstring and get our transitions
dictm = re.compile(r"{.*}",re.S)
state_trans = [eval(dictm.search(getattr(state, 'func_doc', '')).group(0)) for state in state_ptrs]
# use zip to create a tuple containing our state_name/state_transition pairings, and add
# them to the dictionary as key/value pairs
self.state_machine = dict(zip(state_names,state_trans))
def __str__(self):
return '\n'.join([
'\nSTATE_TRANS_DIAGRAM:',
'--------------------',
pprint.pformat(self.state_machine),
'\nSTATE_TRANSITION_TRACE:',
'-----------------------',
'\n'.join(self.state_trace),
])
def run(self, fname = '_state_start', announce = False):
'''
Enter into the state machine here. called run to
simply because it ties in w/ threading module
fname - override the beginning state
announce - print state name as you enter state
'''
self.state_trace = []
while 'None' != fname:
if announce:
print '\n\n %s' % fname.upper().center(60, '*')
self.state_trace.append(fname)
ret = getattr(self, fname)()
fname = self.state_machine[fname][ret]
def _state_start(self):
'''
{'SUCCESS': '_stateA', 'FAIL': '_state_stop'}
'''
return 'SUCCESS'
def _state_stop(self):
'''
{'SUCCESS': 'None', 'FAIL': 'None'}
'''
return 'SUCCESS'
def _stateA(self):
'''
{'SUCCESS': '_stateB','FAIL': '_error'}
'''
error = False
if error:
return 'FAIL'
else:
return 'SUCCESS'
def _stateB(self):
'''
{'SUCCESS': '_stateD','FAIL':'_error'}
'''
error = False
if error:
return 'FAIL'
else:
return 'SUCCESS'
def _stateC(self):
'''
{'SUCCESS': '_stateA','FAIL':'_error'}
'''
error = True
if error:
return 'FAIL'
else:
return 'SUCCESS'
def _stateD(self):
'''
{'SUCCESS': '_stateE','FAIL':'_error'}
'''
error = False
if error:
return 'FAIL'
else:
return 'SUCCESS'
def _stateE(self):
'''
{'SUCCESS': '_stateC','FAIL':'_error'}
'''
error = False
if error:
return 'FAIL'
else:
return 'SUCCESS'
def _error(self):
print "OH NO, AN ERROR!!! <EXITING>"
print self
sys.exit(-1)
if __name__ == '__main__':
# this is a really bad way to do this, probably want python argparse module, but for
# demo purposes, this should allow you to change your start state from the command line
# the simple/dirty way
sm = StateMachineFoo()
start_state = 'None'
if len(sys.argv) > 1:
start_state = sys.argv[1]
sm.run(start_state)
print sm
|
I didn't feel like having a crazy long example, but don't forget that you can dynamically attach a function to this and regenerate the state machine during run time. You can search google or activestate for the sample code if you don't know how.
Give the code a try by saving it as StateMachineFoo.py ... it is set up to error out, you can easily play with the states and change how it works. Once you generate the state machine, you can also modify the string values and switch up the transitions on the fly.
[pre]
python StateMachineFoo.py
or
python StateMachineFoo.py <a start state>
$>python StateMachineFoo.py _state_start
OH NO, AN ERROR!!! <EXITING>
STATE_TRANS_DIAGRAM:
--------------------
{'_stateA': {'FAIL': '_error', 'SUCCESS': '_stateB'},
'_stateB': {'FAIL': '_error', 'SUCCESS': '_stateD'},
'_stateC': {'FAIL': '_error', 'SUCCESS': '_stateA'},
'_stateD': {'FAIL': '_error', 'SUCCESS': '_stateE'},
'_stateE': {'FAIL': '_error', 'SUCCESS': '_stateC'},
'_state_start': {'FAIL': '_state_stop', 'SUCCESS': '_stateA'},
'_state_stop': {'FAIL': 'None', 'SUCCESS': 'None'}}
STATE_TRANSITION_TRACE:
-----------------------
_state_start
_stateA
_stateB
_stateD
_stateE
_stateC
_error`