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

The operation of the state machine is defined by transitions. The transitions control what value is returned and which new state to switch to, given an "event" input when in a certain current "state". State machines have many applications such as games, process controls, and language parsing.

Python, 49 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
class StateMachine:
	'A class that implements a flexible state machine'
	def __init__( self, transitions, first=None ):
		if type( transitions ) == dict:
			self.sm = transitions
		else:
			self.first = first or transitions.split(',')[0]
			smtext = [ term.split(':') for term in transitions.split() ]
			self.sm = dict( [ tuple(a.split(',')), tuple(b.split(',')) ] 
				for a,b in smtext )
		self.setstate()
	def setstate( self, state=None ):
		self.state = state or self.first
	def __call__( self, event ):
		return self.signal( event )
	def signal( self, event ):
		change = self.sm.get( (self.state,event) )
		change = change or self.sm.get( (self.state,'*') )
		change = change or self.sm.get( ('*',event) )
		change = change or self.sm.get( ('*','*') )
		emit, newstate = change or ('**ERROR**',self.first)
		if newstate == '*':
			newstate = self.state
		elif newstate == '':
			newstate = self.first
		if callable( emit ):
			result = emit( self.state, event, newstate )
		else:
			result = ( emit, newstate )
		self.state = newstate
		return result

if __name__ == '__main__':
	# Example for demonstration and test.
	transitions = '''
		garden,climb:go-up-to-roof,roof 
		roof,jump:fall-through,cottage
		cottage,leave:unlock-door,garden 
		*,need-help:can-choose-climb-jump-leave,*
		*,jump:feel-tired,* 
		*,sleep:feel-refreshed,* 
		*,*:cannot-do,*
		'''
	houseguy = StateMachine( transitions, first='garden' )
	print 'I start at location: %s' % houseguy.state
	actionlist = 'sleep jump need-help climb jump jump leave climb climb'.split()
	for action in actionlist:
		result, newstate = houseguy( action )
		print 'I %s. I %s. Location: %s.' % (action, result, newstate)

State machines are useful for storing the "mode" of an object, and controlling which "mode" it moves to when an "event" is detected. The state machine can also output a value when it changes state, so that other objects can react as needed. Several state machines working together can provide very complex behaviour.

The output of the simple example in the code above is:

I start at location: garden
I sleep. I feel-refreshed. Location: garden.
I jump. I feel-tired. Location: garden.
I need-help. I can-choose-climb-jump-leave. Location: garden.
I climb. I go-up-to-roof. Location: roof.
I jump. I fall-through. Location: cottage.
I jump. I feel-tired. Location: cottage.
I leave. I unlock-door. Location: garden.
I climb. I go-up-to-roof. Location: roof.
I climb. I cannot-do. Location: roof.

The transitions argument can be a string of the form "state,event:emit,newstate state,event:emit,newstate etc..." or a dictionary in { (state,event):(emit,newstate), ... } format. Note that each state and event must be a hashable object (strings and tuples are good). The emit object can be callable, so instead of being returned, the value from emit( state, event, newstate ) is returned instead.

This implementation of a state machine class allows the use of wildcard characters for flexibility. The state, event, and newstate values can be supplied with a "*" wildcard. When deciding which state transition to use, the state machine will first look for a (state,event) match, then a (state,"*") match, then a ("*",event) match, and finally a ("*","*") match. It will make the transition based on the first match, or return '**ERROR**' and reset to the first state on failure. When the transition fires, the state is changed to the given newstate, and the emit value (or emit function output) is returned. If the newstate is "*", use the old state again. If the newstate is the empty string "", set the state to the first state.

The wildcard ability allows default operations, catch-all conditions, and error trapping.

Related work: http://code.activestate.com/recipes/146262-finite-state-machine-fsm/?in=lang-python

Created by Mike Sweeney on Wed, 18 May 2011 (MIT)
Python recipes (4591)
Mike Sweeney's recipes (5)

Required Modules

  • (none specified)

Other Information and Tasks