from __future__
from __future__

Reputation: 315

Is yield-based coroutine is REAL coroutine?

I was implementing greenlet API just for practicing.

from greenlet import greenlet

def test1():
    print 12
    gr2.switch()
    print 34

def test2():
    print 56
    gr1.switch()
   print 78

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

here's my messy code

def test1():
    tmp1=yield
    print 12
    try:
        gv2.send(1)
    except StopIteration:
        pass
    tmp1=yield
    print 34

def test2():
    tmp2=yield
    print 56
    try:
        gv2.send(1)
    except StopIteration:
        pass
    tmp1=yield
    print 78

gv1=test1()
gv1.next()
gv2=test2()
gv2.next()

gv1.send(1)

Shows,

12
56
Traceback (most recent call last):
  File "prog.py", line 26, in <module>
    gv1.send(1)
  File "prog.py", line 5, in test1
    gv2.send(1)
  File "prog.py", line 15, in test2
    gv2.send(1)
ValueError: generator already executing

So, I don't know I guess correctly,

but It looks that after test1 send '1' to test2, it still has something,

no control-flow switching happens unlike gevent. test1 still have the flow.

if not, I don't understand what greenlet can do but python "coroutine" can't exists.

My question is

  1. is python coroutine(yield-based) is the real thing(comparing it of others...lisp,ruby,&c)
  2. if right, would you please give some tips for that spagetti code?

Upvotes: 3

Views: 947

Answers (1)

phant0m
phant0m

Reputation: 16905

The generator instance of test2() is sending a value to itself.

def test2():
    tmp2=yield
    print 56
    try:
        gv2.send(1) # this is the offending line
    except StopIteration:
        pass
    tmp1=yield
    print 78

send() will resume the generator, but whenever code is executed inside test2(), it is already running. That is why it throws up.

Did you want to do: gv1.send(1)? That would not work either.

Here is why:

  • Current situation: before gv1.send(1) at the very end of your example
    • gv1 is dormant
    • gv2 is dormant
  • gv1.send(1) is invoked, this resumes gv1
  • gv1 proceeds to gv2.send(1)
  • this resumes gv2
  • gv2 proceeds to gv1.send(1)
  • gv1 is being resumed, however, gv1 has not reached a yield statement since it was last resumed. Ergo, it is still running, which is why it would also throw.

Essentially, the difference could be summarized like this:

  • greenlets share an inherent connection with each other: .switch() pauses the currently executing greenlet and will resume however.
  • generators, on the other hand, are completely independent from each other. There is no shared context in which generators are executed.
    • yield will "pause" a generator
    • next()/ send() will resume a paused generator, invoking them on running generators will result in an exception.

Why are you accessing gv2 (which represents one particular instance of test2) at all? The generator test2() should be self-contained and not make any assumptions about how it is being used. What if you decide, that you want to invoke the generator from some other scope? It doesn't make any sense to send values to yourself anyway: You already have them.

Upvotes: 7

Related Questions