pythongeneratoryield

python - what does yield (yield) do?


Since python 2.5 there is the ability to send(), throw(), close() into a generator. Inside the defined generator one can 'catch' the sent data by doing something like:

def gen():
    while True:
        x = (yield)
        if x == 3:
            print('received 3!!')
            break
        else:
            yield x

What i am trying to play with is doing something like:

def gen2():
    while True:
        yield (yield)

Noticed that it is a legal generator which does something.. First thing i'm trying to figure out is:

Is there a good usage for such writing?

Also when doing something like:

g = gen2()
next(g)
g.send(10) # output: 10
g.send(2) # output: nothing
g.send(3) # output: 3
g.send(44) # output: nothing

Why each second 'send' does not do anything?


Solution

  • 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