Pythonのyield fromでコルーチンとジェネレータを理解する
ジェネレータの停止と再開から、コルーチンの呼び出し構造を読み解く
Copyright © 2026 LWP 山中 一弘
本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。
記事要約
Pythonのジェネレータやコルーチンは、yield、next()、send()、yield from、StopIteration が別々に説明されることが多く、最初は全体像をつかみにくい概念です。特に yield from は「サブジェネレータに処理を委譲する構文」と説明されますが、それだけではなぜコルーチンの呼び出しと関係するのかが見えにくくなります。
この記事では、ジェネレータを「実行途中で止まり、あとから続きに戻れる関数」として捉え直します。そのうえで、yield from が単なる省略記法ではなく、next()、send()、StopIteration、ジェネレータの戻り値をまとめて扱う構文であることを整理します。
結論として、コルーチンを理解する入口は、非同期処理の大きな仕組みから入ることではありません。まずは、ジェネレータの停止位置、ローカル変数、戻り値、委譲関係を順番に追うことです。そこまで分かると、yield from が「呼び出し元と末端の処理をつなぐ通路」として見えてきます。
本記事の対象とゴール
想定読者
Pythonの yield やジェネレータを使ったことはあるが、内部の動きが曖昧な方
yield from を「何となくサブジェネレータを呼ぶ構文」と理解して止まっている方
コルーチン、非同期処理、async / await の前提として、古典的なジェネレータベースの考え方を押さえたい方
実装コード以前に、実行位置と戻り値の流れを言葉で整理したい方
本記事で得られること
ジェネレータが「値を作る関数」だけではなく「状態を保持して止まれる関数」であることが分かります。
yield from が、手書きの委譲コードを置き換える構文であることが分かります。
StopIteration とジェネレータの return 値が、コルーチン理解の重要な部品であることが分かります。
本記事で扱わないこと
async / await の最新構文の網羅的な解説
イベントループ、I/O多重化、スレッド、プロセスの詳細実装
Pythonのバージョンごとの差異を含む厳密な言語仕様の全文解説
先に結論
ジェネレータは、呼び出した瞬間に本文を最後まで実行する関数ではありません。呼び出すとジェネレータオブジェクトが返り、next() されたときに初めて本文が進みます。そして yield に到達すると、値を返すと同時に、実行位置とローカル状態を保持して停止します。
コルーチンは、この「途中で止まり、あとから再開できる」という性質を、処理の分割や非同期的な呼び出しに使う見方です。yield from は、その途中に別のジェネレータを挟んだとき、呼び出し元と末端のジェネレータのやり取りを自然につなぐ構文です。
したがって yield from を理解するには、次の4点を順番に押さえる必要があります。
next() がどの yield を動かすのか
send() された値がどの yield 式に入るのか
サブジェネレータの終了が StopIteration として表れること
ジェネレータの return 値が yield from 式の値になること
この4点がつながると、yield from は単なる構文糖ではなく、コルーチンを階層化するための読みやすい制御構造として理解できます。
第1章(ジェネレータは途中で止まる関数である)
この章では、ジェネレータを「値を順番に返すもの」とだけ見ず、実行状態を持つ関数として整理します。
1.1(普通の関数との違い)
普通の関数は、呼び出されたら本体を実行し、return で値を返して終了します。終了したあとのローカル変数や実行位置は、次回の呼び出しに引き継がれません。
一方、ジェネレータ関数は、呼び出してもすぐには本文を実行しません。呼び出し結果としてジェネレータオブジェクトを返します。そのオブジェクトに対して next() を呼ぶと、初めて本文が進み、最初の yield まで実行されます。
pythondef count():print("start")yield 1yield 2return "done"g = count()print(next(g)) # start と表示され、1 が返る
print(next(g)) # 2 が返る
重要なのは、最初の yield 1 で処理が終わっていないことです。ジェネレータはそこで一時停止し、次の next() で続きから再開します。
1.2(止まるのは値だけではなく状態である)
yield で止まるとき、保持されるのは実行位置だけではありません。ローカル変数も含めて、その時点の実行状態が保持されます。厳密な実装詳細は別として、理解の入口としては「関数の作業中の状態が保存されている」と考えると追いやすくなります。
この見方を持つと、ジェネレータは単なるリストの代用品ではなくなります。途中まで処理した文脈を持ち、次の呼び出しでその文脈へ戻れる部品になります。ここがコルーチン理解への入口です。
第2章(yieldは値を返すだけでなく値を受け取る場所でもある)
この章では、yield を「外へ値を出す命令」としてだけでなく、「外から値を受け取る式」として見ます。
2.1(nextはyieldの右側を外へ返す)
yield 1 のように書くと、next() の呼び出し元へ 1 が返ります。ここだけ見ると、yield は値を生成する命令に見えます。
しかし、yield は式でもあります。つまり、x = yield 1 のように書いたとき、再開時に外から送られた値を x に受け取ることができます。
pythondef receiver():x = yield "ready"yield f"received: {x}"g = receiver()print(next(g)) # readyprint(g.send("abc")) # received: abcこの例では、最初の next() で "ready" が外へ返ります。その後、send("abc") によって、停止していた yield 式の値として "abc" が入り、処理が再開します。
2.2(sendとyieldは対で考える)
send() は単独で理解しようとすると分かりにくい操作です。実際には、停止している yield 式へ値を戻し、その続きから実行を再開する操作です。
したがって、ジェネレータのやり取りは次のように捉えると整理できます。
next() または send() がジェネレータを動かす
ジェネレータは次の yield まで進む
yield の右側の値が呼び出し元へ返る
次に send() された値が、停止していた yield 式の値になる
この往復が分かると、コルーチンは「実行を途中で止め、外と会話しながら再開する関数」として見えてきます。
第3章(yield fromは委譲コードを消す)
この章では、yield from がなぜ必要になるのかを、ジェネレータのネストから整理します。
3.1(サブジェネレータを手で中継すると面倒になる)
あるジェネレータの中から、別のジェネレータを使いたい場面があります。単に値を順番に外へ出すだけなら、次のように書けます。
pythondef sub():yield 1yield 2def outer():for value in sub(): yield valueこの程度なら簡単です。しかし、send()、例外、終了時の戻り値まで中継しようとすると、手書きの委譲コードは急に複雑になります。呼び出し元から見ると outer() を操作しているのに、実際には末端の sub() と値をやり取りしたいからです。
3.2(yield fromは呼び出し元と末端をつなぐ)
yield from を使うと、上の例は次のように書けます。
pythondef outer():result = yield from sub()return resultこのとき outer() は、単に sub() の値を順番に外へ返すだけではありません。呼び出し元からの next()、send()、例外、そしてサブジェネレータの終了まで、サブジェネレータとのやり取りをまとめて引き受けます。
見方を変えると、yield from は「自分はいったん止まり、呼び出し元とサブジェネレータを直接つなぐ」構文です。ここに、コルーチンの委譲構造としての意味があります。
第4章(StopIterationとreturn値をつなげて考える)
この章では、ジェネレータの終了がどのように表れるかを整理します。yield from の理解では、ここが特に重要です。
4.1(ジェネレータはStopIterationで終わる)
ジェネレータがすべての yield を出し終えると、次の next() で StopIteration が発生します。これはエラーというより、イテレーションが終わったことを知らせる仕組みです。
pythondef sub():yield 1return "sub done"g = sub()print(next(g)) # 1print(next(g)) # StopIterationreturn "sub done" は、普通の関数のように呼び出し元へ直接返るわけではありません。ジェネレータでは、終了時の値として StopIteration に含まれます。
4.2(yield from式の値はサブジェネレータの終了値になる)
yield from の重要な点は、サブジェネレータの return 値を受け取れることです。
pythondef sub():yield 1return "sub done"def outer():result = yield from sub()yield f"result: {result}"g = outer()print(next(g)) # 1print(next(g)) # result: sub doneこの例では、sub() が return "sub done" で終了し、その値が yield from sub() 式の値として result に入ります。
ここが、単なる for value in sub(): yield value との大きな違いです。yield from は、サブジェネレータが出した途中の値だけでなく、終了時の値まで含めて委譲関係を扱います。
第5章(コルーチンの呼び出しとして見る)
この章では、ここまでの部品をコルーチンの呼び出しとして整理します。
5.1(nextが末端の処理を動かす)
outer() が yield from sub() を持っている場合、呼び出し元は outer() に対して next() します。しかし、その操作は最終的に sub() の yield を動かします。
呼び出し元から見ると、途中にいくつジェネレータが挟まっているかを意識しなくても、末端の処理から値が返ってきます。逆方向に send() した値も、停止している末端の yield 式へ届きます。
この性質によって、コルーチンを小さな部品に分けても、呼び出し側のコードを極端に複雑にせずに済みます。
5.2(非同期コルーチン理解への接続)
ジェネレータベースのコルーチンでは、処理は1本の流れの中で進みます。あるコルーチンが yield from で別の処理に委譲すると、自分は停止し、委譲先が動きます。委譲先がさらに別の処理に委譲することもあります。
このとき重要なのは、呼び出し元の next() やイベントループ側の駆動が、最終的にどの停止点を動かしているのかを追うことです。yield from は、その途中の中継作業を構文として引き受け、戻り値まで元の場所へ運びます。
現在のPythonでは async / await が一般的ですが、考え方の土台には「途中で止まり、あとで再開し、結果を戻す」という構造があります。yield from を読むことは、その土台を理解する訓練になります。
第6章(実務での理解の仕方)
この章では、実務でジェネレータやコルーチンを読むときの見方をまとめます。
6.1(構文名より流れを追う)
yield from という名前だけを見ると、値を外へ出す yield の延長に見えます。しかし実際には、外へ出す値、外から入る値、終了時の値、例外の流れをまとめて扱う構文です。
そのため、コードを読むときは、構文名だけで判断せず、次を順番に確認します。
最初に next() されるジェネレータはどれか
最初に止まる yield はどこか
send() された値はどの yield 式に入るか
サブジェネレータの return 値を誰が受け取るか
最終的な結果はどの階層まで戻るか
この順序で追うと、複数段のジェネレータ委譲でも見失いにくくなります。
6.2(まずは小さい例で止まる場所を見る)
コルーチンの理解でいきなり非同期I/Oやイベントループから入ると、用語が多すぎて混乱します。最初は、2つか3つの小さなジェネレータだけを使い、next() のたびにどこまで進むか、return 値がどこへ入るかを確認するほうが有効です。
そのうえで、yield from が「中間のジェネレータが大量にあっても、末端の停止点と呼び出し元をつなぐ構文」だと見えてくると、コルーチンの呼び出し構造を読みやすくなります。
まとめ
ジェネレータは、単に値を順番に返す仕組みではありません。途中で停止し、実行位置とローカル状態を保持し、次の呼び出しで再開できる関数です。この性質があるからこそ、ジェネレータはコルーチン理解の入口になります。
yield from は、サブジェネレータの値を外へ流すだけの構文ではありません。next()、send()、StopIteration、サブジェネレータの return 値をまとめて扱い、呼び出し元と末端の処理をつなぎます。
コルーチンを読むときは、非同期処理という大きな言葉から入るよりも、まず「どの next() が、どの yield を動かし、どの戻り値がどこへ返るのか」を追うほうが確実です。その流れが見えれば、yield from は難解な構文ではなく、複雑な委譲を読みやすくするための構造として理解できます。
出典メモ
入力リンクファイル: C:\Users\hoehoe\マイドライブ\LWP記事\03過去記号\CS設計手法\2018-06-11 PG コルーチンとジェネレータ - Togetter.URL
元URL: https://togetter.com/li/1236145
正規URL: https://posfie.com/@hoehoe1234/p/R3xkkrW
元記事タイトル: 2018-06-11 PG コルーチンとジェネレータ
元記事説明: パイソンについて徒然に・・・。
元記事公開日時: 2018-06-11T13:50:15+09:00
取得日: 2026-05-15
