1 .. #!/usr/bin/env python
2 # -*- coding: iso-8859-1 -*-
6 Generic state machine class using iterators
7 +++++++++++++++++++++++++++++++++++++++++++
11 :Copyright: 2006 Guenter Milde.
12 Released under the terms of the GNU General Public License
15 Detailed documentation of this class and the design rationales (including
16 tested variants) is available in the file simplestates-test.py.txt
19 """Simple generic state machine class using iterators
24 Example: A two-state machine sorting numbers in the categories
30 Import the basic class::
32 >>> from simplestates import SimpleStates
34 Subclass and add state handlers:
36 >>> class StateExample(SimpleStates):
37 ... def high_handler_generator(self):
39 ... for token in self.data_iterator:
41 ... self.state = "low"
45 ... result.append(token)
47 ... def low_handler_generator(self):
49 ... for token in self.data_iterator:
51 ... self.state = "high"
55 ... result.append(token)
59 Set up an instance of the StateExample machine with some test data::
61 >>> testdata = [1, 2, 3, 4, 5, 4, 3, 2, 1]
62 >>> testmachine = StateExample(testdata, state="low")
64 >>> print [name for name in dir(testmachine) if name.endswith("generator")]
65 ['high_handler_generator', 'low_handler_generator']
71 Iterating over the state machine yields the results of state processing::
73 >>> for result in testmachine:
76 [1, 2, 3] [5, 4] [2, 1]
78 For a correct working sort algorithm, we would expect::
80 [1, 2, 3] [4, 5, 4] [3, 2, 1]
82 However, to achieve this a backtracking algorithm is needed. See iterqueue.py
83 and simplestates-test.py for an example.
86 The `__call__` method returns a list of results. It is used if you call
87 an instance of the class::
90 [[1, 2, 3], [5, 4], [2, 1]]
94 Abstract State Machine Class
95 ============================
100 """generic state machine acting on iterable data
104 state -- name of the current state (next state_handler method called)
105 state_handler_generator_suffix -- common suffix of generator functions
106 returning a state-handler iterator
109 state_handler_generator_suffix = "_handler_generator"
114 * sets the data object to the `data` argument.
116 * remaining keyword arguments are stored as class attributes (or methods, if
117 they are function objects) overwriting class defaults (a neat little trick
118 I found somewhere on the net)
120 ..note: This is the same as `self.__dict__.update(keyw)`. However,
121 the "Tutorial" advises to confine the direct use of `__dict__`
122 to post-mortem analysis or the like...
126 def __init__(self, data, **keyw):
127 """data -- iterable data object
128 (list, file, generator, string, ...)
129 **keyw -- all remaining keyword arguments are
130 stored as class attributes
133 for (key, value) in keyw.iteritems():
134 setattr(self, key, value)
140 Iteration over class instances
141 ------------------------------
143 The special `__iter__` method returns an iterator. This allows to use
144 a class instance directly in an iteration loop. We define it as is a
145 generator method that sets the initial state and then iterates over the
146 data calling the state methods::
149 """Generate and return an iterator
151 * ensure `data` is an iterator
152 * convert the state generators into iterators
153 * (re) set the state attribute to the initial state
154 * pass control to the active states state_handler
155 which should call and process self.data_iterator.next()
157 self.data_iterator = iter(self.data)
158 self._initialize_state_generators()
159 # now start the iteration
161 yield getattr(self, self.state)()
163 a helper function generates state handlers from generators. It is called by
164 the `__iter__` method above::
166 def _initialize_state_generators(self):
167 """Generic function to initialise state handlers from generators
169 functions whose name matches `[^_]<state>_handler_generator` will
170 be converted to iterators and their `.next()` method stored as
173 suffix = self.state_handler_generator_suffix
174 shg_names = [name for name in dir(self)
175 if name.endswith(suffix)
176 and not name.startswith("_")]
177 for name in shg_names:
178 shg = getattr(self, name)
179 setattr(self, name[:-len(suffix)], shg().next)
181 Use instances like functions
182 ----------------------------
184 To allow use of class instances as callable objects, we add a `__call__`
188 """Iterate over state-machine and return results as a list"""
189 return [token for token in self]
194 running this script does a doctest::
196 if __name__ == "__main__":