![]() |
Prothon
Tutorial
|
![]() |
Prothon Home | Previous Page | Tutorial Outline | Next Page |
In section 4.2 we learned that the "for" statement uses iterators to generate the sequence of values for each loop, and that an iterator is simply a function that responds to repeated calls of a method "next()" by returning values until it signals the end of the sequence with an exception. Prothon has a "gen" keyword to make a "gen" statement that looks identical to the "def" statement with "def" replaced by "gen": "gen name(params): body". So "gen" defines a function-like object with a name, formal parameters, and a code body. The difference is that the object created is always an iterator, not a simple function. You can think of the word "generator" as meaning that it "generates" an iterator, or that the iterator it produces "generates" values. It works either way. For this discussion we will use the second meaning, that it generates values when it runs. The generator has to use another keyword, "yield" in order to do the actual generation of values. Yield is somewhat like the "return" keyword in that it has an argument that is used as a return value when "yield" is executed, but unlike "return" the function execution is not terminated. Instead, the state of the function is stored away, or "frozen" if you want to think of it that way so that the function can resume execution at the statement after the yield later when another value is needed. So in it's simplest form, the "gen" is like a function that runs until it hits a "yield" and then returns the "yield" value. Then it is paused until "next()" is called on the iterator again and then the function resumes execution until it hits the next yield, returns the second value, pauses again, etc. This continues until the function hits a return or runs off the end of the code block at which time it signals that it is done. # Prothon source file tut16.pr gen odds(max): num = 1 while num < max: yield num num += 2 for i in odds(10): print i, # prints 1 3 5 7 9 Prothon also allows you to call other functions with yields in them to build up more complex code structure. When doing so, only the one outermost "function" should use the "gen" keyword, because it is the one producing an iterator, not being called. The others are being called as normal. # Prothon source file tut17.pr gen evenOdds(max): def evens(max): num = 0 while num < max: yield num num += 2 def odds(max): num = 1 while num < max: yield num num += 2 evens(max) odds(max) for i in evenOdds(10): print i, # prints 0 2 4 6 8 1 3 5 7 9 In order to explain how generators and the yield statement works, I need to explain a bit about function calling in Prothon. Prothon is "stackless". What this means is that the interpreter maintains a data structure that holds the stack of Prothon objects, stack pointer, code data, program counter, and various scope objects for each "execution frame" totally seperate from any operating system stack. An execution frame is the data needed by each scope of running code. Whenever a function call happens, a new "execution frame" is created and put on the list of running frames. When a function returns, that frame is usually destroyed after the return value is copied from it. When a yield statement is executed, things are done a bit differently. A reference to the current execution frame is stored in the generator object instead of being destroyed. If it was a normal function defined by a "def" keyword, then the next one is stored. This is repeated until one is stored that was created by a "gen" keyword instead of a "def" keyword. At this point all the frames are stored away in the generator object and the "yield" value is returned via the "next()" call. When the next "next()" call comes in, the generator object (the iterator) takes the execution frames it has saved away and puts them all back on the active frames list and restarts the code where it left off. This sounds like it might be slow but in actuality it only involves moving a reference pointer for each function and is very fast. There are exceptional conditions when the normal flow of code needs to be interrupted and you need to quit what you are doing and get out of your current scope fast. These conditions can range from errors like I/O errors, divide-by-zero errors, to more normal events that are just expected less often, like running out of some resource. Prothon has an exception mechanism that handles these situations by "raising an exception" which causes execution to be interrupted at the current location and resumed at some location outside of the current scope. This is much like the "break", "return", or "yield" statements except that it can happen anywhere. When an exception is raised, Prothon creates an Exception object. This is an object that has the special object Exception somewhere in its prototype chain. The exception object may also have an attribute called "doc_" which contains more information about the exception. "doc_" may be a string or may not, but printing the string version of the exception obj by calling it via "obj.str_()" will usually give something useful to view. Prothon has a statement pair called "try" and "except" that are used to manage exceptions. The form of the pair is "try: try_block", "except proto, name: exception_block". The try_block is executed and if any exception occurs inside it, then exection is halted and the except statement is checked to see if the "proto" in the except statement is in the prototype chain of the exception object. If it is, then the exception object is stored in "name" as a local variable of the "exception_block" and the "exception_block" is executed. Proto may be a list of prototypes. If "proto" is not in the chain, then the interpreter continues outward looking for an except statement that does match. Whenever it leaves an "execution frame" that frame information is kept for viewing in an exception stack (which we will see in a moment) and then the execution frame is destroyed. If no except statement matches, then that thread is killed and an error message is printed on the error console showing the exception information and the exception stack. # Prothon source file tut18.pr def func(): for i in 4: print i, 1/(i-2) try: func() except Exception, err: print "Exception:", err print func()Output results (cleaned up): 0 -.5 1 -1 Exception: Divide by zero Error 0 -.5 1 -1 Uncaught exception: --- File: C:\Prothon\pr\tutorial\tut18.pr, line: 8, char: 6 --- File: C:\Prothon\pr\tutorial\tut18.pr, line: 6, char: 24 Divide by zero Error You can also raise an exception with the "raise" keyword. It has the form "raise exc-obj, doc-obj". The "exc-obj" is the exception object to be raised and must have Exception in its prototype chain. The "doc-obj" may be any object but is usually a string. The "doc-obj" is stored as an attribute of the "exc-obj" with the name "doc_". |
Prothon Home | Previous Page | Tutorial Outline | Next Page |