The idea of a cogenerator is that it is to a generator what a
coroutine is to a routine. As coroutines can pass control to fellow
coroutines, so can cogenerator pass 'yielding control' to fellow
cogenerators. The function co()
and the objects
HERE, GOTO, GOSUB, RETURN
are all defined in the file cogenerator.py (download here).
A cogenerator function is a generator function that can yield
HERE
, GOTO
and GOSUB
objects,
and raise a RETURN
object. The cogenerator could be run
as a normal, everyday generator but that's not taking advantage of it
at all. A cogenerator function cogenf can be turned into a generator
function using the co()
function: genf =
co(cogenf)
yield HERE
doesn't yield anything. It 'returns'
the cogenerator currently being run.yield GOTO(cogen[, val])
doesn't yield anything.
It passes 'yielding control' to cogen
. This is a way
of 'flattening' cogenerators. If specified, val
is sent
to cogen.yield GOSUB(cogen[, val])
is like yield
GOTO(...)
, but when cogen
is exhausted,
'yielding control' is passed back to the current cogeneratorraise RETURN(val)
ends the current cogenerator (a
bit like a raise StopIteration
) but it sends val to its
parent cogenerator (i.e. the one that GOSUB
ed to
it)In the following examples I use a function called
trace()
that prints all the values yielded by a generator
prefixed by their index. Below is the definition of this function
def trace(gen): for x in enumerate(gen): print "%s\t%s" % x
GOTO
- two cogenerators passing control to each
other>>> def cofoo(): ... self = yield HERE ... other = cobar(self) ... yield 'foo start' ... msg_from_cobar = yield GOTO(other) ... yield 'foo middle, cobar says "%s"' % msg_from_cobar ... yield GOTO(other) ... yield 'foo end' ... >>> def cobar(other): ... yield 'bar start' ... yield GOTO(other, 'hello') ... yield 'bar middle' ... yield GOTO(other) ... # We'll never get past here... ... yield 'bar end' ... >>> foo = co(cofoo) # make a generator function out of cofoo >>> trace(foo())0 foo start 1 bar start 2 foo middle, cobar says "hello" 3 bar middle 4 foo end>>>
GOSUB
- flattening nested cogeneratorsNote that co()
can be used as a decorator if the
cogenerator it decorates doesn't need to be GOTO
ed to
from any other cogenerator.
>>> @co ... def main_gen(): ... yield 'start main' ... result = yield GOSUB(nested_cogen()) ... yield 'returned %s' % result ... yield GOTO(nested_cogen()) ... yield 'end main' # Never happens ... >>> def nested_cogen(): ... yield 'nested' ... raise RETURN(1) ... >>> trace(main_gen())0 start main 1 nested 2 returned 1 3 nested>>>
The example below takes a list or tuple and flattens it. It is
defined as a recursive cogenerator. Note that co()
can't
be used as a decorator here as coflatten()
called from
within itself.
>>> def coflatten(x): ... if isinstance(x, (list, tuple)): ... for i in x: ... yield GOSUB(coflatten(i)) ... else: ... yield x ... >>> flatten = co(coflatten) >>> trace(flatten([1, [2, [[3], 4]], 5]))0 1 1 2 2 3 3 4 4 5>>>