Prepare for Github,
[pylit.git] / doc / examples / simplestates.py.txt
blob2a259b28bfdda0646f0b24df21b82d9e7a994236
1 ..  #!/usr/bin/env python
2   # -*- coding: iso-8859-1 -*-
3   
4 simplestates.py
5 ***************
6 Generic state machine class using iterators
7 +++++++++++++++++++++++++++++++++++++++++++
9 :Version:   0.2
10 :Date:      2006-12-01
11 :Copyright: 2006 Guenter Milde.
12             Released under the terms of the GNU General Public License 
13             (v. 2 or later)
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
20   
21   Usage
22   =====
23   
24   Example: A two-state machine sorting numbers in the categories 
25            "< 3" and ">= 3".
26   
27   Preparation
28   -----------
29   
30   Import the basic class::
31   
32   >>> from simplestates import SimpleStates
33   
34   Subclass and add state handlers:
35   
36   >>> class StateExample(SimpleStates):
37   ...    def high_handler_generator(self):
38   ...        result = []
39   ...        for token in self.data_iterator:
40   ...            if token <= 3:
41   ...                self.state = "low"
42   ...                yield result
43   ...                result = []
44   ...            else:
45   ...                result.append(token)
46   ...        yield result
47   ...    def low_handler_generator(self):
48   ...        result = []
49   ...        for token in self.data_iterator:
50   ...            if token > 3:
51   ...                self.state = "high"
52   ...                yield result
53   ...                result = []
54   ...            else:
55   ...                result.append(token)
56   ...        yield result
57   
58   
59   Set up an instance of the StateExample machine with some test data::
60   
61   >>> testdata = [1, 2, 3, 4, 5, 4, 3, 2, 1]
62   >>> testmachine = StateExample(testdata, state="low")
63   
64   >>> print [name for name in dir(testmachine) if name.endswith("generator")]
65   ['high_handler_generator', 'low_handler_generator']
66   
67   
68   Running
69   -------
70   
71   Iterating over the state machine yields the results of state processing::
72   
73   >>> for result in testmachine:
74   ...     print result,
75   ...
76   [1, 2, 3] [5, 4] [2, 1]
77   
78   For a correct working sort algorithm, we would expect::
79     
80     [1, 2, 3] [4, 5, 4] [3, 2, 1]
81   
82   However, to achieve this a backtracking algorithm is needed. See iterqueue.py
83   and simplestates-test.py for an example.
84   
85   
86   The `__call__` method returns a list of results. It is used if you call 
87   an instance of the class::
88   
89   >>> testmachine()
90   [[1, 2, 3], [5, 4], [2, 1]]
91   
92   """
93   
94 Abstract State Machine Class
95 ============================
99   class SimpleStates:
100       """generic state machine acting on iterable data
101       
102       Class attributes:
103       
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
107       """
108       state = 'start'
109       state_handler_generator_suffix = "_handler_generator"
110   
111 Initialisation
112 --------------
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 
131           """
132           self.data = data
133           for (key, value) in keyw.iteritems():
134               setattr(self, key, value)
135   
136   
137   
138   
139   
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::
148       def __iter__(self):
149           """Generate and return an iterator
150           
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()
156           """
157           self.data_iterator = iter(self.data)
158           self._initialize_state_generators()
159           # now start the iteration
160           while True:
161               yield getattr(self, self.state)()
162   
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
168           
169           functions whose name matches `[^_]<state>_handler_generator` will
170           be converted to iterators and their `.next()` method stored as
171           `self.<state>`. 
172           """
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)
180   
181 Use instances like functions
182 ----------------------------
184 To allow use of class instances as callable objects, we add a `__call__`
185 method::
187       def __call__(self):
188           """Iterate over state-machine and return results as a list"""
189           return [token for token in self]
190   
191 Command line usage
192 ==================
194 running this script does a doctest::
196   if __name__ == "__main__":
197       import doctest
198       doctest.testmod()