Generator - Send values to Python coroutine without handling StopIteration

De openkb
Aller à : Navigation, rechercher

Sommaire

Questions

Given a Python coroutine:

def coroutine():
     score = 0
     for _ in range(3):
          score = yield score + 1

I d like to use it in a simple loop like this:

cs = coroutine()
for c in cs:
     print(c)
     cs.send(c + 1)

... which I would expect to print

1
3
5

But actually, I get an exception on the line yield score + 1:

 TypeError: unsupported operand type(s) for +:  NoneType  and  int 

I can get it to work if I call next manually:

c = next(cs)
while True:
    print(c)
    try:
        c = cs.send(c + 1)
    except StopIteration:
        break

But I don t like that I need to use try/except, given that generators are usually so elegant.

So, is there any way to use a finite coroutine like this without explicitly handling StopIteration? I m happy to change both the generator and the way I m iterating over it.

Second Attempt

Martijn points out that both the for loop and my call to send advance the iterator. Fair enough. Why, then, can t I get around that with two yield statements in the coroutine s loop?

def coroutine():
    score = 0
    for _ in range(3):
        yield score
        score = yield score + 1

cs = coroutine()
for c in cs:
    print(c)
    cs.send(c + 1)

If I try that, I get the same error but on the send line.

0
None
Traceback (most recent call last):
  File "../coroutine_test.py", line 10, in <module>
    cs.send(c + 1)
TypeError: unsupported operand type(s) for +:  NoneType  and  int 

Answers

I ll take a stab at your second attempt. First, let coroutine be defined as:

def coroutine():
    score = 0
    for _ in range(3):
        yield
        score = yield score + 1

This function will output your 1, 3, 5 as in the original question.

Now, let s convert the for loop into a while loop.

# for loop
for c in cs:
    print(c)
    cs.send(c + 1)

# while loop
while True:
    try:
        c = cs.send(None)
        print(c)
        cs.send(c + 1)
    except StopIteration:
        break

Now, we can get this while loop working using the following if we precede it with a next(cs). In total:

cs = coroutine()
next(cs)
while True:
    try:
        c = cs.send(None)
        print(c)
        cs.send(c + 1)
    except StopIteration:
        break
# Output: 1, 3, 5

When we try to convert this back into a for loop, we have the relatively simple code:

cs = coroutine()
next(cs)
for c in cs:
    print(c)
    cs.send(c + 1)

And this outputs the 1, 3, 5 as you wanted. The issue is that in the last iteration of the for loop, cs is already exhausted, but send is called again. So, how do we get another yield out of the generator? Let s add one to the end...

def coroutine():
    score = 0
    for _ in range(3):
        yield
        score = yield score + 1
    yield

cs = coroutine()
next(cs)
for c in cs:
    print(c)
    cs.send(c + 1)
# Output: 1, 3, 5

This final example iterates as intended without a StopIteration exception.

Now, if we take a step back, this can all be better written as:

def coroutine():
    score = 0
    for _ in range(3):
        score = yield score + 1
        yield # the only difference from your first attempt

cs = coroutine()
for c in cs:
    print(c)
    cs.send(c + 1)
# Output: 1, 3, 5

Notice how the yield moved, and the next(cs) was removed.

Source

License : cc by-sa 3.0

http://stackoverflow.com/questions/35469386/send-values-to-python-coroutine-without-handling-stopiteration

Related

Outils personnels
Espaces de noms

Variantes
Actions
Navigation
Outils