Python: yield and yield assignment

Charlie Haley picture Charlie Haley · Aug 20, 2015 · Viewed 8.8k times · Source

How does this code, involving assignment and the yield operator, work? The results are rather confounding.

def test1(x): 
    for i in x:
        _ = yield i 
        yield _
def test2(x): 
    for i in x:
        _ = yield i 

r1 = test1([1,2,3])
r2 = test2([1,2,3])
print list(r1)
print list(r2)

Output:

[1, None, 2, None, 3, None] 
[1, 2, 3]

Answer

Jeremy Brown picture Jeremy Brown · Aug 20, 2015

The assignment syntax ("yield expression") allows you to treat the generator as a rudimentary coroutine.

First proposed in PEP 342 and documented here: https://docs.python.org/2/reference/expressions.html#yield-expressions

The client code that is working with the generator can communicate data back into the generator using its send() method. That data is accessible via the assignment syntax.

send() will also iterate - so it actually includes a next() call.

Using your example, this is what it would be like to use the couroutine functionality:

>>> def test1(x):
...     for i in x:
...         _ = yield i
...         yield _
...
>>> l = [1,2,3]
>>> gen_instance = test1(l)

>>> #First send has to be a None
>>> print gen_instance.send(None)
1
>>> print gen_instance.send("A")
A
>>> print gen_instance.send("B")
2
>>> print gen_instance.send("C")
C
>>> print gen_instance.send("D")
3
>>> print gen_instance.send("E")
E
>>> print gen_instance.send("F")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Note that some of the sends are lost because of the second yield in each loop iteration that doesn't capture the sent data.

EDIT: Forgot to explain the Nones yielded in your example.

From https://docs.python.org/2/reference/expressions.html#generator.next:

When a generator function is resumed with a next() method, the current yield expression always evaluates to None.

next() is used when using the iteration syntax.