前の投稿で、yieldによって値を受け渡したり、ジェネレーターの呼び出し元とジェネレーターの中を行き来したりすることができるようになったのを見た。

ただ、ここには一つ制約があって、ジェネレーターが別のジェネレーターを呼び出すときに問題が生じる・・らしい (todo: が、その問題が何なのか、まだよく分かってないので、分かったら書き直す)。 これを回避するために、トランポリンというテクニックを使う。

def foo(val):
    yield f"foo: {val}"

def bar():
    ret = yield foo("bar")
    print(ret)
    yield

def baz():
    b = bar()
    f = b.send(None)
    ret = f.send(None)
    b.send(ret)

baz()

これは、次のように動作する:

  1. baz()呼び出しによって、bazの中身がスタート。ジェネレーターbが生成される
  2. b.send(None)によって、barの中身がスタート。yield foo("bar")によって、ジェネレーターが生成される。これがyieldによってbazに返され、fに代入される。また、制御がbazの方に戻る
  3. f.send(None)によって、fooの中身がスタート。yield f"foo: {val}"によって、値"foo: bar"が生成される。これがyieldによってbazに返され、retに代入される
  4. b.send(ret)によって、barの中身に制御が戻り、(barの中の)retに値が代入される。これがprint(ret)によって表示され、次のyieldbazに制御が戻る

ポイントは何かというと・・ジェネレーターと呼び出し元との行き来は、全て、一番根っこの呼び出し元とジェネレーターとの間で行われる、ということ(つまり、上の例で言うと、foobazbarbazの間でyieldによる行き来は起こるが、ジェネレーターであるbarbazの間では起こらないということ)。

なので、根っこの呼び出し元のbazが、全てのジェネレーターを保持し、全ての返り値を受け取り、それを適切なジェネレーターにsendするという役割を一手に引き受けている。