Step 5 - A runnable Example

Lets take a look at our current concept by using a runnable example.

Loop v1
def loop(coroutine):
    """Loop v1"""

    step = 1
    while True:
        print("Loop step", step)
        try:
            next(coroutine)
            step += 1
        except StopIteration as e:
            return e.value
from loop import loop


def generator_one():
    print(generator_one.__name__, 1)
    yield
    print(generator_one.__name__, 2)


def generator_two():
    print(generator_two.__name__, 1)
    yield
    print(generator_two.__name__, 2)
    yield
    print(generator_two.__name__, 3)


def chaining_generator():
    print(chaining_generator.__name__, 1)
    yield from generator_one()
    print(chaining_generator.__name__, 2)
    yield from generator_two()
    print(chaining_generator.__name__, 3)


def main_generator():
    print(main_generator.__name__, 1)
    yield from chaining_generator()
    print(main_generator.__name__, 2)


result = loop(main_generator())
print("Loop finished with result", result)

Output:

Loop step 1
main_generator 1
chaining_generator 1
generator_one 1
Loop step 2
generator_one 2
chaining_generator 2
generator_two 1
Loop step 3
generator_two 2
Loop step 4
generator_two 3
chaining_generator 3
main_generator 2
Loop finished with result None

As a sequence diagram:

sequenceDiagram participant l as loop participant mg as Main Generator participant cg as Chaining Generator participant g1 as Generator One participant g2 as Generator Two activate l activate l l->>mg: next deactivate l activate mg activate mg mg->>cg: yield from deactivate mg activate cg activate cg cg->>g1: yield from deactivate cg activate g1 activate g1 g1-->>l: yield deactivate g1 activate l l->>g1: next deactivate l activate g1 g1->>cg: return deactivate g1 deactivate g1 activate cg cg->>g2: yield from deactivate cg activate g2 activate g2 g2-->>l: yield deactivate g2 activate l l->>g2: next deactivate l activate g2 g2-->>l: yield deactivate g2 activate l l->>g2: next deactivate l activate g2 g2->>cg: return deactivate g2 deactivate g2 activate cg cg->>mg: return deactivate cg deactivate cg activate mg mg->>l: return deactivate mg deactivate mg deactivate l

Summary

We have learned so far:

  • yield suspends execution of a coroutine.

  • Using yield from suspends the execution back to the initial caller at the root - the loop.

  • The loop resumes the execution where the last yield was executed.

  • A loop is easiest to be implemented as a function that gets a single coroutine passed as argument. This coroutine may call other coroutines.