A simple understanding of the generator-based state machine in Python _python

Source: Internet
Author: User
Tags abs generator sin in python

Simple generators have many advantages. In addition to being able to express the process of a class of problems in a more natural way, the generator greatly improves many inefficiencies. In Python, function calls are expensive; In addition to other factors, it takes some time to resolve the list of function arguments (in addition to other things, analyze positional and default parameters). Initialization of the frame object also takes some steps to build (there are more than 100 C language programs, as Tim Peters said on Comp.lang.python), and I haven't checked the Python source code myself yet. Conversely, restoring a generator is rather labour-saving; The parameters are parsed, and the frame object is "idle" for recovery (with little additional initialization required). Of course, if speed is the most important, you should not use a dynamic language that has been compiled with bytecode, but it's better to be faster than slow even if the speed is not the primary consideration.
Recall State Machine

In another article in front of "cute Python," I introduced the StateMachine class, how many state handlers are required for a given machine, and how many state handlers it allows the user to add. In the model, one or more states are defined as final (end state), and only one state is defined as the initial state (the call class method configures this). Each handler has a required structure, the handler performs a series of actions, and then, in a moment, it returns to the loop in the Statemachine.run () method with a token that indicates the next state to want. Similarly, using the cargo variable allows a state to pass some (unhandled) information to the next state.

The typical use of the StateMachine Class I'm introducing is to use input in a stateful fashion. For example, I used a text processing tool (txt2html) to read several lines of content from a file, and it needs to be handled in a special way, depending on the category to which each row belongs. However, you often need to look at the context provided in the previous lines to determine which category the current row belongs to (and how it should be handled). The implementation of this procedure built on the StateMachine class can define a handler that reads a few lines and then processes the rows in a way that resembles a. Soon, a condition was met, so that the next batch of several lines should be handled by the B-processing program. A passes control back to the. Run () loop, indicating that the switch to the B state--and any extra rows that B should handle before reading an extra few lines--is not handled correctly by any A. Finally, a handler passes its control to a state that is specified as final, processing the Stop (halt).

For the specific code examples in the previous section, I used a simplified application. I handle the stream of numbers generated by an iterative function, rather than the processing of multiple lines of content. Each state handler prints only those numbers in the desired range of numbers (and some messages about the active state). When a number in a digital stream reaches a different range, a different handler takes over "processing." For this part, we'll look at another way to implement the same digital stream processing with a generator (with some extra tricks and functionality). However, a more complex generator example might handle the input stream more like the one mentioned in the previous paragraph. Let's take a look at the previous state machine's version of the deletion code:
Listing 1. statemachine_test.py

From StateMachine import StateMachine
def ones_counter (val):
  print ' ones state:  ' while
  1:
    if Val <= 0 or Val >=:
      newstate = "Out_of_range"; break
    Elif <= val <:
      newstate = "twenties"; 
   break
    elif <= val <:
      newstate = "TENS";     Break
    Else:
      print "@%2.1f+"% val,
    val = Math_func (val)
  print ' >> ' return
  (newstate, VA L)
# ... other handlers
... def math_func (n): From the
  math import sin return
  abs (sin (n)) *31
if __name__== "__main__":
  m = StateMachine ()
  m.add_state ("ones", Ones_counter)
  m.add_state ("TENS", Tens_counter) m.add_state ("
  Twenties ", Twenties_counter)
  m.add_state (" Out_of_range ", None, end_state=1)
  m.set_start (" ones ")
  M.run (1)

Readers should look at the previous article if they are interested in how the StateMachine class is imported and how its methods work.


using the builder

The full version of the

Generator based state machine is slightly longer than the code sample I would prefer to introduce in this column. However, the following code sample is a complete application and there is no need to import a separate StateMachine module to provide support. In general, this version is shorter than the one based on the class (we'll see that it's something special and very powerful).
Listing 2. stategen_test.py

From __future__ Import generators import sys def math_gen (n): # iterative function becomes a generator from math impor T sin while 1:yield n n = abs (sin (n)) *31 # Jump targets not state-sensitive, A to simplify example def JUMP_ To (Val): if 0 <= val < 10:return ' Ones ' Elif <= val < 20:return ' TENS ' elif <= val < 30: Return ' twenties ' Else:return ' Out_of_range ' def Get_ones (ITER): Global cargo while 1:print "\nones Sta TE: "While jump_to (cargo) = = ' ones ': print" @%2.1f "% cargo, cargo = Iter.next () yield (ca 
      rGO), Cargo def get_tens (ITER): Global cargo while 1:print "\ntens state:" While jump_to (cargo) = = ' Tens ': Print "#%2.1f"% cargo, cargo = Iter.next () yield (jump_to (cargo), cargo) def get_twenties (ITER): Glob
      Al cargo while 1:print "\ntwenties state:" While jump_to (cargo) = = ' Twenties ': print ' *%2.1f '% cargo, Cargo = Iter.next ()
    Yield (jump_to (cargo), cargo) def exit (iter): jump = Raw_input (' \n\n[co-routine for jump?] '). Upper () print "... Jumping into middle of ', jump yield (jump, Iter.next ()) print "\nexiting from Exit () ..." Sys.exit () def scheduler (GE NDCT, start): global cargo coroutine = start while 1: (coroutine, cargo) = Gendct[coroutine].next () if __name__
       = = "__main__": Num_stream = Math_gen (1) cargo = Num_stream.next () gendct = {' Ones ': Get_ones (Num_stream),     ' TENS ': Get_tens (Num_stream), ' Twenties ': get_twenties (Num_stream), ' Out_of_range ': Exit (Num_stream)

 } Scheduler (GENDCT, jump_to (cargo))

There are a lot of places to study on generator based state machines. The 1th is superficial to a large extent. We schedule stategen_test.py to use only functions, not classes (at least, I mean, the builder has a sense of functional programming rather than object-oriented programming (OOP). However, if you want, you can easily wrap the same generic model into one or more classes.

The main function in our sample is scheduler (), which is completely generic (but much shorter than the statemachine in the previous pattern). The function Scheduler () requires the builder-iterator object dictionary (the instantiated builder) as an argument. The string name given to each generator can be any name you want--the literal name of the Builder factory function is an obvious choice, but I use uppercase keyword names in the example. The scheduler () function also accepts "initial state" as an argument, but you may be able to automatically select a default value if you wish.

Each "scheduled" generator follows some simple conventions. Each generator runs for a period of time and then produces a pair of values, including the desired jump and a "cargo"--just like the previous model. No generator is explicitly marked as "final State". Instead, we allow each generator to choose to produce an error to end Scheduler (). In special cases, if the generator "leaves" the final state or reaches a return state, the generator generates a Stopiteration exception. You can catch this exception (or a different exception) if you want. In our example, we use Sys.exit () to terminate the application, and we will encounter this sys.exit () in the exit () builder.

Pay attention to two small questions about the code. The sample above uses a simpler loop generator-iterator instead of using an iterative function to generate our numeric sequence. Instead of continuously returning the "last value", the generator emits an (infinite/indeterminate) number stream with each subsequent call. This is a small but useful generator sample. Moreover, the above "state transition" is isolated in a separate function. In the actual program, the state transition jump is context-sensitive and may be determined in the actual generator body. This approach simplifies the sample. Although it may be of little use, but let's hear it, we can create a generator function from a function factory to further simplify it, but in general, each generator will not be similar to other generators enough to make this method practical.

Cooperative program and semi-cooperative program

The attentive reader may have noticed that, in fact, we have unwittingly entered a flow control structure that is much more useful than initially indicated. In the sample code, it's not just a state machine. In fact, the above model is a very effective common system of collaborative programs. Most readers may need some background information here.

A cooperative program is a collection of program functions that allows arbitrary branching to other control contexts as well as any resumption of streams from branch points. The subroutines that we are familiar with in most programming languages are a very limited branch of a generic collaboration program. The subroutine only enters from a fixed point at the top and exits only once (it cannot be restored). The subroutine also always sends the spread back to its caller. In essence, each cooperative program represents a callable continuation--although adding a new word does not necessarily clarify its meaning to someone who does not know the word. Randall Hydean's Art of assembly's "Cocall Sequence Between Two processes" illustrations are of great help in interpreting collaborative procedures. A link to this diagram is available on the resources. There is also a link to the comprehensive discussion of Hyde in the Resources section, which is quite a good discussion.

Regardless of the negative impact, you will notice that the infamous goto statement in many languages also allows arbitrary branching, but in a less structured context it can cause "macaroni code."

The Python 2.2+ generator is a big step toward a collaborative program. The big step is that the generator--unlike a function/subroutine--is recoverable and can get a value after multiple invocations. However, the Python generator is nothing more than the "semi-collaborative program" described by Donald Knuth. The generator is recoverable and can be controlled elsewhere--but it can only branch control back to the caller who called it directly. Rather, the builder context (as with any context) can call other generators or functions on its own--even if it makes recursive calls on its own--but each final return must be passed through the linear hierarchy of the return context. Python generators do not take into account common collaborative programming usage of "producer" and "Consumer" (you can continue from the middle of each other at will).

Fortunately, it's fairly easy to emulate a fully equipped collaboration program with a Python generator. The simple trick is the scheduler () function, which is very similar to the generator in the sample code above. In fact, the state machine we put forward is a much more common cooperative program framework model. Adapting to this pattern overcomes the small flaws that still exist in the Python generator (allowing unwary programmers to play the full power of macaroni code).


Stategen in the Operation

The easiest way to get an accurate picture of what's going on in stategen_test.py is to run it:
Listing 3. Run Stategen (Manual jump Control)

% python stategen_test.py
ones State:    @ 1.0
Twenties State: *26.1 *25.3 ones State
:    @ 4.2
Twenties State:  *26.4  *29.5  *28.8
TENS State:    #15.2  #16.3  #16.0
Ones State:    @ 9.5  @ 3.8
TENS State:    #18.2  #17.9
Twenties State:  *24.4
TENS State :    #19.6
Twenties State:  *21.4
TENS State:    #16.1  #12.3
ones State:    @ 9.2  @ 6.5  @ 5.7
TENS State:    #16.7
Twenties State:  *26.4  *30.0
[Co-routine for Jump?] Twenties ...
 Jumping into middle of twenties
Twenties State:
TENS State:    #19.9
Twenties State:  *26.4  *29.4  *27.5  *22.7
TENS State:    #19.9
Twenties State:  *26.2  *26.8
Exiting from Exit () ...

This output is essentially the same as the output in the previous statemachine_test.py. Each row in the result represents a stream that is used in a particular handler or builder, and the flow context is declared at the beginning of the row. However, whenever another collaborative program branch goes inside the builder, the generator version resumes execution (within a loop), not just the handler function again. Assuming that all get_* () collaborative programs are contained in an infinite loop, this difference is less pronounced.

To understand the essential differences in stategen_test.py, look at what is happening in the exit () builder. The first time a builder-iterator is invoked, a jump target is collected from the user (this is a simple case of an event-driven branching decision that is likely to be exploited in a real-world application). However, when the exit () is called again, it is in a later stream context of the generator--Displays the exit message, and invokes Sys.exit (). The user in the interaction sample can jump directly to "out_of_range" without having to go to another "handler" to exit (but it will execute a recursive jump to the same generator).


Concluding remarks

As I said in the introduction, I expect the state machine version of the collaboration program to run much faster than the previous version of Class (Class-with-callback-handler) with the callback handler. Recovery Builder-iterators are much more efficient. The specific example is so simple that it is hardly enough to be judged, but I welcome the reader's feedback on the specific results.

But whatever progress I have introduced in the "collaborative program model" in terms of speed, it will be overshadowed by the astonishing universal flow control it has achieved. Many readers on the Comp.lang.python newsgroup have asked how Python's new builder is universal. I think the usability of the framework I described was answered: "As you want!" "For most of the things that are related to Python, programming on something is usually much simpler than understanding them." Try my mode; I think you'll find it useful.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.