The yield from grammar in Python 3

  • 2020-05-24 05:47:38
  • OfStack

preface

Recently, I have been playing with Autobahn. It gives an example based on asyncio. Failed. pip install asyncio When I reported invalid syntax directly, I thought there was something wrong with the processing of to3 -- I can't blame you, since package was written with 2 and then converted to 3 -- I found out that asyncio only supported version 3.3+. Then I went back to the code and found a sentence yield from ; yield I know, but yield from What is?

PEP-380

Okay, so I came up with the title google, yield from All lives and all lives are in this PEP, but the general idea is original yield The statement only gives control of CPU back to the direct caller. If you want to refactor the logic of an generator or coroutine with yield statement into another generator, it will be very troublesome, because the generator on the outside will be responsible for sending messages to the generator on the inside. So someone had an idea for python to encapsulate messaging and make it transparent to the programming ape, and there it was yield from .

Specifies the PEP - 380 yield from The semantics, or the nested generator should have the behavior pattern.

Suppose that the A function has a statement like this


yield from B()

B() If it returns an iterable (iterable) object, b, then A() will return an generator -- a, according to our naming convention -- then:

Each value generated by the b iteration is passed directly to the caller of a. All values sent to a via the send method are passed directly to b. If the value sent is None, b's is called __next__() Method, otherwise the send method of b is called. If an StopIteration exception is generated on the method call to b, a continues execution yield from The following statement, while other exceptions will be propagated to a, leading to the execution of a yield from Throws an exception when the. If an exception other than GeneratorExit is passed from throw to a, the exception is passed directly from throw to b. If b's throw method throws StopIteration, a continues to execute; Other exceptions cause a to throw exceptions as well. If an GeneratorExit exception is called by throw into a, or if a's close method is called, and b also has close methods, b's close method is also called. If this method of b throws an exception, it causes a to throw an exception as well. Conversely, if b succeeds and close drops, a will also throw an exception, but a specific GeneratorExit exception. In the a yield from The expression evaluates to the first parameter of the StopIteration exception thrown at the end of the b iteration. In the b return <expr> The statement actually throws it StopIteration(<expr>) Exception, so the value of return in b becomes a yield from The return value of an expression.

Why are there so many requirements? Because the behavior of something like generator becomes very complex with the addition of the throw method, especially in the case of several generator in 1, it requires a process-managing-like meta-language to operate on it. All the above requirements are to unify the complicated behavior of 1 generator, so it is not easy.

I admit I didn't see what the authors of PEP were trying to say, so it might help to "refactor" it once.

A useless example

It doesn't work because you probably don't really want to write a program like this, but... Anyway, that's enough.

Imagine an generator function:


def inner():
 coef = 1
 total = 0
 while True:
 try:
  input_val = yield total
  total = total + coef * input_val
 except SwitchSign:
  coef = -(coef)
 except BreakOut:
  return total

The generator generated by this function accumulates the values received from the send method into the local variable total, and stops iterating when the BreakOut exception is received. As for the other SwitchSign exception, it should be easy to understand, but I won't reveal the plot here.

From the code point of view, by inner() The resulting generator receives data for operations through send, while the throw method accepts control from external code to execute different branches of code, so far so clear.

And then because there's a change in demand, we need to inner() Before and after this code is added the initialization and cleanup code. Since I thought "don't touch the code until it's broken," I decided to let it inner() I'm going to keep the status quo, and I'm going to write another one outer() , and put the added code in outer() And provide with inner() 1 type of operation interface. Due to the inner() It takes advantage of several features of generator, so outer() Here are five things you must do:

outer() 1 generator must be generated; In each iteration of step 1, outer() To help inner() Returns the iteration value; In each iteration of step 1, outer() To help inner() Receiving data sent from outside; In each iteration of step 1, outer() To deal with inner() Receive and throw all exceptions; in outer() By close, inner() Also be properly close dropped.

According to the above requirements, in a world with only yield, outer() It might look something like this:


def outer1():
 print("Before inner(), I do this.")
 i_gen = inner()
 input_val = None
 ret_val = i_gen.send(input_val)
 while True:
 try:
  input_val = yield ret_val
  ret_val = i_gen.send(input_val)
 except StopIteration:
  break
 except Exception as err:
  try:
  ret_val = i_gen.throw(err)
  except StopIteration:
  break
 print("After inner(), I do that.")

WTF, this code ratio inner() It's even longer, and it hasn't handled the close operation yet.

Now let's try alien technology:


def outer2():
 print("Before inner(), I do this.")
 yield from inner()
 print("After inner(), I do that.")

In addition to meeting all of the above requirements, these four lines of code save some paper when printed.

We can outer1() and outer2() It is not difficult to find that the behavior of the two generator is basically 1. In this case, alien technology is of course the first choice in most cases.

Questions about generator and coroutine

I have seen coroutine under Python before and I think it is strange. I can see their behavior pattern clearly, but I don't know why I should use this pattern. generator and coroutine have one kind of external interface. What puzzles me most is that coroutine under Python ties the two operations of "messaging" and "scheduling" to one yield -- even if it does yield from I don't see the need to do that. The concept of coroutine would have been easier to understand if 1 had separated the two semantics syntactically from the beginning and designed a set of interfaces for generator and coroutine respectively.

conclusion

The above is the whole content of this article, I hope the content of this article can bring 1 definite help for everyone to study or use python, if you have any questions, you can leave a message to communicate.


Related articles: