Yield and generators (i.e. save stack; return value to caller; receive value from caller) are really a language feature for writing a runtime for a different language with coroutines. Or if you're willing to write your program in a way that looks like it was generated by a source-to-source transformation tool, you can write your coroutines on top of them. The most basic construct is something like this:
CurrentCoroutine = None
def run(main, arg):
global CurrentCoroutine
CurrentCoroutine = main
while CurrentCoroutine is not None
CurrentCoroutine, arg = CurrentCoroutine.send(arg)
def corodecorator(coro):
@functools.wraps(coro):
def init():
c = coro()
c.next()
return c
return init
And this is pretty much it. A simple example for two coroutines that pass control to each other would be: