This code is practical mainly in the trivial sense that it runs to completion.

# -*- coding: utf-8 -*-
"""
Created on Sun Jan  3 13:51:20 2016

@author: kurner
"""
import itertools

def assemble():
    drv = yield None
    drv.result = 1
    drv = yield drv
    drv.result += 1
    drv.name = "Alvin"
    drv = yield drv
    drv.result += 1
    drv.color = "Red"
    drv = yield drv
    drv.result += 1
    drv.age = 100
    return drv

class Driver:
   
    def __init__(self, g):
        self.g = g
        self.g.send(None) # primed
        self.counter = 1

    def go(self):
        while True:
            try:
                self.counter *= 10
                self = self.g.send(self)
            except StopIteration as exc:
                return exc.args[0].__dict__
           
d = Driver(assemble())
r = d.go()
print(r)  

Console output:

{'name': 'Alvin', 'color': 'Red', 'age': 100, 'counter': 10000, 'g': <generator object assemble at 0x110078af0>, 'result': 4}

The tractor classes I posted earlier should more properly by referred to as co-routines, as they were driven by a "tick" loop.  Python itself is growing more powerful in this area.

What stands out about assemble() is it has yields, so is clearly a generator, but also it returns, and the StopIteration value is not only recoverable, but what we expect more generally from a 'yield from'.

What assemble returns is a thing that keeps pushing the coroutine with a send(), actually sending itself, meaning it grows attributes from both inside and outside the coroutine.  I dump the state of said "thing" as the end result.

One starts to see the relevance to concurrency when imagining many coroutines such as assemble() that keep relinquishing control while holding their place, each advancing as driven to do so by a "driver" (task) that whips in on.

Kirby