Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 163 lines
  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`