python - what does yield (yield) do?

yield (yield) first yields None from the inner yield. It then receives a value from send or next. The inner yield evaluates to this received value, and the outer yield promptly yields that value.


Each yield conceptually has two parts:

  1. Transmit a value to the caller of send or next.
  2. Receive a value from the next send or next call.

Similarly, each send or next conceptually has two parts:

  1. Transmit a value to the yield expression that the generator is currently paused at. (This value is None for next.)
  2. Receive a value from the next yield expression.

The most confusing part of the system is probably that these parts are staggered. The two parts of a yield correspond to two different invocations of send or next, and the two parts of a send or next correspond to two different yields.

If we work through a simple example:

def gen():
    print('Not ran at first')
    yield (yield)

g = gen()  # Step 1
print(next(g))  # Step 2
print(g.send(1)  # Step 3
g.send(2)  # Step 4

Here's how things work out:

Inside the generator                      Outside the generator

Step 1

                                          g calls gen()
g returns a generator object 
without executing the print
just yet statement.
                                          >>> g
                                          <generator object gen at 0x7efe286d54f8>

Step 2

                                          next(g) sends None to g
g receives None, ignores it
  (since it is paused at the start
   of the function)

g prints ('not ran at first')

g executes the "transmit" phase
  of the inner yield, transmitting
  None
                                          next(g) receives None

Step 3

                                          g.send(1) sends 1 to g
g executes the "receive" phase
  of the inner yield, receiving 1
g executes the "transmit" phase
  of the outer yield, transmitting 1
                                          g.send(1) receives 1 from g

Step 4

                                          g.send(2) sends 2 to g
g executes the "receive" phase
  of the outer yield, receiving 2
g reaches the end of gen and raises
  a StopIteration
                                          g.send(2) raises the StopIteration
                                          from g

yield is an expression. The value of the expression is the value of whatever was sent using .send, or None if nothing was sent (including if next was used instead of .send). .send is a method call and thus of course also returns a value, which is the value yielded by the generator. In other words, every time you .send, a value (which may be None) is yielded, and every time you yield, a value (which may be None) is sent.

Here is a simple example:

def gen():
    sent1 = yield 1
    print(sent1, "was sent")
    sent2 = yield 2
    print(sent2, "was sent")
    print("Reached end of generator")

g = gen()
print(next(g), "was yielded")
print(g.send("A"), "was yielded")
print(g.send("B"), "was yielded")
next(g)

# output
1 was yielded
A was sent
2 was yielded
B was sent
Reached end of generator
# StopIteration is raised here

In your example, the first next yields None, since the first yield is the inner yield in yield (yield) (i.e., the one in parentheses). The first send passes 10 as the value this yield. Each subsequent value that you send becomes the value of one of the yields. The reason some of your send calls produce no output is that the inner yield specifies no value, so it yields None. As mentioned above, when you call send, a value is yielded; in your case, that value is None for the inner yield, so no output is displayed at the interactive prompt. The outer yield, on the other hand, does specify a value, namely the result of the inner yield. So when you send a value in to the inner yield, it will be yielded by the outer yield at the next iteration. (I'm assuming you're referring to output at the interactive prompt; if you run your code as a script, there will be no output at all, since you never print anything or otherwise produce explicit output.)

Here is another example that may be illuminating:

def gen():
    yield (yield (yield (yield "WHOA")))

>>> g = gen()
>>> next(g)
'WHOA'
>>> g.send(1)
1
>>> g.send(2)
2
>>> g.send(3)
3
>>> g.send(4)
Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    g.send(4)
StopIteration

Notice that each time a value is sent in, it is immediately yielded back. This is because each yield yields the value of a more-deeply-nested yield. Each yield "becomes" the sent value and is immediately yielded by the next yield in the chain. This continues until all yields are exhausted and StopIteration is raised.

Similar questions about this have been asked before. My impression is that confusion tends to arise because people expect send to "just send" a value. But that is not the case. Using send advances the generator and yields the next result, just like using next. You can think of next(gen) as equivalent to gen.send(None).