Pythonのyield fromでコルーチンとジェネレータを理解する

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 の前提として、古典的なジェネレータベースの考え方を押さえたい方

  • 実装コード以前に、実行位置と戻り値の流れを言葉で整理したい方

本記事で得られること

  1. ジェネレータが「値を作る関数」だけではなく「状態を保持して止まれる関数」であることが分かります。

  2. yield from が、手書きの委譲コードを置き換える構文であることが分かります。

  3. StopIteration とジェネレータの return 値が、コルーチン理解の重要な部品であることが分かります。

本記事で扱わないこと

  • async / await の最新構文の網羅的な解説

  • イベントループ、I/O多重化、スレッド、プロセスの詳細実装

  • Pythonのバージョンごとの差異を含む厳密な言語仕様の全文解説

先に結論

ジェネレータは、呼び出した瞬間に本文を最後まで実行する関数ではありません。呼び出すとジェネレータオブジェクトが返り、next() されたときに初めて本文が進みます。そして yield に到達すると、値を返すと同時に、実行位置とローカル状態を保持して停止します。

コルーチンは、この「途中で止まり、あとから再開できる」という性質を、処理の分割や非同期的な呼び出しに使う見方です。yield from は、その途中に別のジェネレータを挟んだとき、呼び出し元と末端のジェネレータのやり取りを自然につなぐ構文です。

したがって yield from を理解するには、次の4点を順番に押さえる必要があります。

  1. next() がどの yield を動かすのか

  2. send() された値がどの yield 式に入るのか

  3. サブジェネレータの終了が StopIteration として表れること

  4. ジェネレータの return 値が yield from 式の値になること

この4点がつながると、yield from は単なる構文糖ではなく、コルーチンを階層化するための読みやすい制御構造として理解できます。

第1章(ジェネレータは途中で止まる関数である)

この章では、ジェネレータを「値を順番に返すもの」とだけ見ず、実行状態を持つ関数として整理します。

1.1(普通の関数との違い)

普通の関数は、呼び出されたら本体を実行し、return で値を返して終了します。終了したあとのローカル変数や実行位置は、次回の呼び出しに引き継がれません。

一方、ジェネレータ関数は、呼び出してもすぐには本文を実行しません。呼び出し結果としてジェネレータオブジェクトを返します。そのオブジェクトに対して next() を呼ぶと、初めて本文が進み、最初の yield まで実行されます。

python
def count():
print("start")
yield 1
yield 2
return "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 に受け取ることができます。

python
def receiver():
x = yield "ready"
yield f"received: {x}"
g = receiver()
print(next(g))        # ready
print(g.send("abc"))  # received: abc

この例では、最初の next() で "ready" が外へ返ります。その後、send("abc") によって、停止していた yield 式の値として "abc" が入り、処理が再開します。

2.2(sendとyieldは対で考える)

send() は単独で理解しようとすると分かりにくい操作です。実際には、停止している yield 式へ値を戻し、その続きから実行を再開する操作です。

したがって、ジェネレータのやり取りは次のように捉えると整理できます。

  1. next() または send() がジェネレータを動かす

  2. ジェネレータは次の yield まで進む

  3. yield の右側の値が呼び出し元へ返る

  4. 次に send() された値が、停止していた yield 式の値になる

この往復が分かると、コルーチンは「実行を途中で止め、外と会話しながら再開する関数」として見えてきます。

第3章(yield fromは委譲コードを消す)

この章では、yield from がなぜ必要になるのかを、ジェネレータのネストから整理します。

3.1(サブジェネレータを手で中継すると面倒になる)

あるジェネレータの中から、別のジェネレータを使いたい場面があります。単に値を順番に外へ出すだけなら、次のように書けます。

python
def sub():
yield 1
yield 2
def outer():
for value in sub():
    yield value

この程度なら簡単です。しかし、send()、例外、終了時の戻り値まで中継しようとすると、手書きの委譲コードは急に複雑になります。呼び出し元から見ると outer() を操作しているのに、実際には末端の sub() と値をやり取りしたいからです。

3.2(yield fromは呼び出し元と末端をつなぐ)

yield from を使うと、上の例は次のように書けます。

python
def outer():
result = yield from sub()
return result

このとき outer() は、単に sub() の値を順番に外へ返すだけではありません。呼び出し元からの next()、send()、例外、そしてサブジェネレータの終了まで、サブジェネレータとのやり取りをまとめて引き受けます。

見方を変えると、yield from は「自分はいったん止まり、呼び出し元とサブジェネレータを直接つなぐ」構文です。ここに、コルーチンの委譲構造としての意味があります。

第4章(StopIterationとreturn値をつなげて考える)

この章では、ジェネレータの終了がどのように表れるかを整理します。yield from の理解では、ここが特に重要です。

4.1(ジェネレータはStopIterationで終わる)

ジェネレータがすべての yield を出し終えると、次の next() で StopIteration が発生します。これはエラーというより、イテレーションが終わったことを知らせる仕組みです。

python
def sub():
yield 1
return "sub done"
g = sub()
print(next(g))  # 1
print(next(g))  # StopIteration

return "sub done" は、普通の関数のように呼び出し元へ直接返るわけではありません。ジェネレータでは、終了時の値として StopIteration に含まれます。

4.2(yield from式の値はサブジェネレータの終了値になる)

yield from の重要な点は、サブジェネレータの return 値を受け取れることです。

python
def sub():
yield 1
return "sub done"
def outer():
result = yield from sub()
yield f"result: {result}"
g = outer()
print(next(g))  # 1
print(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 の延長に見えます。しかし実際には、外へ出す値、外から入る値、終了時の値、例外の流れをまとめて扱う構文です。

そのため、コードを読むときは、構文名だけで判断せず、次を順番に確認します。

  1. 最初に next() されるジェネレータはどれか

  2. 最初に止まる yield はどこか

  3. send() された値はどの yield 式に入るか

  4. サブジェネレータの return 値を誰が受け取るか

  5. 最終的な結果はどの階層まで戻るか

この順序で追うと、複数段のジェネレータ委譲でも見失いにくくなります。

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