(GO) [Python learning] yield 2.5
After shhgs published "py 2.5 What's new yield", I didn't pay special attention to yield usage, because yield added in 2.3 is relatively simple in functionality, it is an indispensable statement as a generator. As long as the function that contains it is a generator. However, in 2.3, generator cannot be re-imported, modified during running, or exception is not thrown. You can either call in sequence or create a new generator. Yield in generator is just a statement. However, after version 2.5, the situation has changed significantly.
Yield is not described too much in the shhgs article, which leads to many problems in my understanding, so I carefully studied what's new and PEP 342 documents, and described them below.
Here we will not explain why we need to modify yield, but only the function.
1. yield is an expression. It is no longer a statement, but can be placed on a separate row. Original article:
Redefine "yield" to be an expression, rather than a statement. The current yield statement wocould become a yield expression whose value is thrown away.
As you can see, if you still write statements, it is actually an expression, but its value is thrown away.
Then a yield expression can be written as follows:
X = yield I
Y = x + (yield X)
How does this mechanism work? It is easy to understand in Version 2.3. You can fully understand the yield statement as a "return" statement, but after the "return" statement is complete, the function does not end, but runs intermittently, until the yield statement is encountered again. So Version 2.5 is not just a "return" statement, so let's describe it after reading the following description about send.
2. added the send (MSG) method, so you can use it to send messages to generator. Original article:
Add a new send () method for generator-iterators, which resumes the generator and "sends" a value that becomes the result of the current yield-expression. the send () method returns the next value yielded by the generator, or raises stopiteration if the generator exits without yielding another value.
Executing a send (MSG) operation will resume the running of the generator, and the sent value will becomeCurrentYield ExpressionReturn Value. Then send () will return the value of the next generator yield. If there is no value for yield, an exception is thrown.
As you can see, this actually contains a run, starting from assigning MSG to the currently stopped yield expression, to the end of the next yield statement, and then returning the parameters of the next yield statement, then, wait for the next call. It's really complicated to understand. I don't know if you understand it.
Let's start by imagining that yield is easy to understand.
We can think of yield as the following pseudocode:
X = yield I ==> put (I); X = wait_and_get ()
As you can see, it can be understood as a put (I) First, which is the parameter after the yield expression. If yield does not have a parameter, it indicates none. It indicates placing I in a global buffer, which is equivalent to returning a value.
Wait_and_get () can be understood as a blocking call. It waits for the outside world to wake it up and can return a value.
After this conversion, it is much easier to understand. Let's take an example:
>>> Def g ():
Print 'step 1'
X = yield 'hello'
Print 'step 2', 'X = ', X
Y = 5 + (yield X)
Print 'step 3', 'y = ', y
It's easy. Each step shows a status and prints the value of the relevant variable. Let's take a look.
>>> F = g ()
>>> F. Next ()
Step 1
'Hello'
What did you see. When we execute next (), the code stops when it runs to X = yield 'hello' and returns 'hello' after yield '. If we replace the above program with pseudo code to see what it looks like:
Def g ():
Print 'step 1'
Put ('hello') # x = yield 'hello'
X = wait_and get ()
Print 'stpe 2', 'X = ', X
Put (X)
Y = 5 + wait_and_get ()
Print 'step 3', 'y = ', y
We can see from the pseudo code that when next () is called for the first time, a 'hello' is returned first, and then the program hangs on X = wait_and_get (), which is the same as the execution result.
Let's continue:
>>> F. Send (5)
Step 2 x = 5
5
This time we used send (5) instead of next. Note that next () in 2.5 is only a manifestation of send (none. As demonstrated by pseudo-code, send () is a value that activates wait_and_get () and returns 5 of the send (5) parameter through it, so the value of X is 5, print 'step 2', return the value of X 5, and then the program hangs on Y = 5 + wait_and_get (), consistent with the running result.
If we continue:
>>> F. Send (2)
Step 3 Y = 7
Traceback (most recent call last ):
File "<pyshell #13>", line 1, in <module>
F. Send (2)
Stopiteration
We can see that wait_and_get () is activated first, and 2 of send (2) is returned through it. Therefore, the value of Y is 7, and then the following print statement is executed, however, because no yield statement is followed, the program cannot be suspended, and an exception is thrown.
From the above examples of pseudocode and Analysis of running results, I think you should be clear about yield. Note the following:
- Next () is equivalent to send (none)
- No parameter after yield indicates that the return value is none.
There are several important words in the document:
Because generator-iterators begin execution at the top of the generator's function body, there is no yield expression to receive a value when the generator has just been created. therefore, calling send () with a non-None argument is prohibited when the generator iterator has just started, and a typeerror is raised if this occurs (presumably due to a logic error of some kind ). thus, before you can communicate with a coroutine you must first call next () or send (none) to advance its execution to the first yield expression.
This means that the first call either uses next () or send (none), and cannot use send () to send a non-none value, the reason is that for the first time there is no yield expression to accept this value. It is easy to understand if you convert it to pseudo code. The preceding example shows that the first sentence after conversion is a put () instead of wait_and_get (). Therefore, the first execution can only return data, but cannot accept data. If you send a non-none value, a typeerror exception is thrown. Let's try it:
>>> F = g ()
>>> F. Send (5)
Traceback (most recent call last ):
File "<pyshell #15>", line 1, in <module>
F. Send (5)
Typeerror: Can't send non-none value to a just-started Generator
You can see it. It's really wrong.
3. added the throw () method, which can be used to throw exceptions from inside the generator to control the execution of the generator. Test:
>>> F = g ()
>>> F. Send (none)
Step 1
'Hello'
>>> F. Throw (generatorexit)
Traceback (most recent call last ):
File "<pyshell #17>", line 1, in <module>
F. Throw (generatorexit)
File "<pyshell #6>", line 3, in G
X = yield 'hello'
Generatorexit
>>> F. Send (5)
Traceback (most recent call last ):
File "<pyshell #18>", line 1, in <module>
F. Send (5)
Stopiteration
As you can see, after the first execution, I executed a f. Throw (generatorexit), so this exception was thrown. If you execute F. Send (5) again, we can see that the generator has been stopped. Generatorexit is a newly added exception class. Its Description:
A new standard exception is defined, generatorexit, inheriting from exception. A generator shoshould handle this by re-raising it (or just not catching it) or by raising stopiteration.
It can be seen that the purpose of adding it is to give the generator the opportunity to execute some cleaning work during exit. This is used in the thumbnail example after pep 342.
4. added the close method. It is used to close a generator. Its pseudocode is as follows (copied from the document ):
Def close (Self ):
Try:
Self. Throw (generatorexit)
Before t (generatorexit, stopiteration ):
Pass
Else:
Raise runtimeerror ("generator ignored generatorexit ")
# Other exceptions are not caught
It can be seen that a generatorexit exception is thrown to itself first. If generator causes a generatorexit or stopiteration exception, the operation is disabled successfully. If generator returns a value, runtimeerror is thrown. If it is another exception, it will not be processed, which is equivalent to raising the layer and being processed by the upper layer code. The example is also described in the thumbnail example in pep 342.
There are other changes that will not be further described.
The examples in pep 342 are also interesting. To put it simply, I don't know much about it.
There are four examples in this document, which are actually composed of two examples.
Two examples, 1 and 2, complete the processing of a thunmbnail. In the first example, consumer is actually a decorator, which encapsulates a generator and is mainly used to call next (). Why, because the next call can use send () A non-none value, so that the subsequent code can directly use send () when using Generator () non-none value. In the second example, the thumbnails of a series of images are processed. Here, each image is processed as a generator, and the processing of image files is a top-level generator. The top-level generator calls the generator of each image processing. In addition, this example also provides protection when an exception exits: After processing the picture being processed, exit.
The echo server is demonstrated in two examples. 3. After a scheduler is completed, 4 is to convert listen processing and socket-connected handle processing to a schedulable generator on the basis of 3, and schedule it in the scheduler. At the same time, we can see that the socket uses non-blocking processing.
Through the above learning, I deeply feel that yield is indeed very delicate. This is only a deeper understanding after talking with shhgs. Many things can be better and more exquisite through generator, is a very interesting thing. As a result, shhgs feels that yield in 2.5 is more meaningful than. I hope you will understand it together.
But to be honest, yield is really hard to understand. You need to understand it carefully.