Pythonのyieldは、値を一括で返す関数ではなく、必要な分だけ値を取り出せるジェネレータを作る構文です。大量データ、ログ処理、APIのページング、ストリーム処理では、listに全件を詰めるよりも、イテレータとして順次扱う設計のほうがメモリを抑えやすくなります。そのため、コード最適化や性能向上を考える場面では、returnとの違い、next()の動き、StopIterationの扱いを早い段階で整理しておくと理解しやすくなります。
公式ドキュメントでは、yield式はジェネレータ関数の本体で使われ、関数の実行状態を保持したまま呼び出し元へ値を返す仕組みとして説明されているのが一般的です。詳しい仕様はPython公式ドキュメントのyield式と、反復処理全体はPython公式ドキュメントのイテレータ型で確認できます。
- Python 3.12
- requests 2.31.0
- Windows、macOS、Linuxの標準ターミナルを想定
yieldとreturnの違いを、ジェネレータの状態保持から理解できますnext()、send()、yield fromを使った反復処理を整理できます- イテレータとしてデータを流す設計が、コード最適化や性能向上に効く場面を判断できます
StopIteration、無限ループ、return値の扱いでつまずきやすい点を確認できます- ファイル、API、リアルタイムデータ処理にジェネレータを使う実装パターンを学べます
Pythonのyield文とは?
yieldを含む関数は、呼び出した瞬間に通常の値を返すのではなく、ジェネレータオブジェクトを返します。このオブジェクトはイテレータとして振る舞い、for文やnext()から要求されたタイミングで関数本体を少しずつ進めます。そのため、Pythonで大きなデータ列を扱うときに、必要な値だけを生成する設計へ切り替えられますし、ここがポイントです。
yield文の基本的な概念
これを通常の関数と比べると、違いは実行状態の保存にあるのが現実的です。returnは関数を終了させて値を返しますが、yieldは値を返した場所で関数を一時停止し、ローカル変数や次に実行する位置を保持します。次回のnext()では、停止したyieldの直後から処理が再開されますが、これは押さえたい点です。
その仕組みにより、ジェネレータは全件分のリストを先に作らず、値が必要になった時点で計算できます。たとえばログファイルの各行、データベースから分割取得したレコード、センサー値のような連続データは、イテレータとして流すほうが自然です。こうした処理では、コード最適化の第一候補としてyieldを検討できると整理できます。
💡 Tips: yieldを使う目的は、単に短いコードを書くことではありません。値を生成するタイミングを遅らせ、メモリ使用量や処理の待ち時間を制御しやすくする点にあります。| 観点 | リスト | ジェネレータ | 使い分け |
|---|---|---|---|
| 生成タイミング | 全要素を先に作る | 要求時に作る | 全件参照ならリスト、逐次処理ならジェネレータ |
| メモリ | 要素数に比例 | 状態保持分が中心 | 大きな入力ではジェネレータが扱いやすい |
| 再利用 | 同じ値を何度も走査しやすい | 基本的に一方向へ消費する | 再走査が必要なら再生成を考える |
| 代表構文 | []、list() | yield、() | 用途で構文を選ぶ |
| 終了 | 要素数で終わる | StopIterationで終わる | for文なら例外処理は隠蔽される |
| 連携 | インデックス参照しやすい | パイプライン化しやすい | 前処理をつなぐならジェネレータ |
| 性能向上 | 小規模なら読みやすい | 大規模でメモリ削減しやすい | 入力サイズで判断する |
サンプルコード1:シンプルなジェネレータ
具体的には、偶数を順番に返す関数をyieldで書くと、呼び出し側は通常のイテレータと同じ感覚で扱えます。even_numbers()は全ての偶数をリスト化せず、for文が値を要求するたびに次の偶数を返します。
結果: 期待される出力は、0から8までの偶数が1行ずつ表示される形です。
結果: 期待される表示例として、0、2、4、6、8が順に並びます。
このとき、iの値はyieldで一時停止したあとも失われません。次の反復で同じ関数本体が再開されるため、状態を外部変数に退避しなくても連続した値を作れます。Pythonのジェネレータがコード最適化に使われる理由は、この状態保持と遅延評価を自然に書ける点にあると理解できます。
yield文の使い方
基本構文が見えたら、状態を持つ処理、別ジェネレータへの委譲、無限シーケンス、パイプライン化へ広げると実務的な判断がしやすくなります。yieldは単独で値を返すだけでなく、yield from、while True、range()、for文と組み合わせることで、イテレータの流れを組み立てます。
サンプルコード2:ステートを持つジェネレータ
その代表例が、現在値を内部に持つカウンターです。counter()はstartから始まり、呼び出しのたびにstepずつ値を増やすると覚えるとよいでしょう。countは関数内のローカル変数ですが、ジェネレータの一時停止中も値が保持されます。
結果: 期待される出力は、10、12、14が順番に表示される形です。
この構造は、ID採番、ページ番号、一定間隔の時刻生成などに応用できます。ただし、while Trueは終了条件を持たないため、呼び出し側で取り出す回数を制御する必要があります。無限に続くイテレータは柔軟ですが、停止条件の設計がないと予期しない長時間処理につながりますし、これが一つの目安です。
サンプルコード3:yield fromの利用例
yield fromは、別のイテレータやジェネレータから値をそのまま外側へ流す構文です。内側のsquare_numbers()が作る値を、外側のdelegate_generator()が中継するため、ループのネストを浅くできると考えられます。
結果: 期待される出力は、入力値を2乗した数値が1行ずつ表示される形です。
結果: 期待される表示例として、1、4、9、16、25が並びます。
一方で、単純な中継だけならyield fromは読みやすくなりますが、途中でログ出力や例外変換を挟む場合は明示的なfor文のほうが意図を追いやすい場合もあります。委譲の目的が値の横流しか、加工を含むのかで書き方を分けると、保守しやすいコードになると言えるでしょう。
サンプルコード4:無限シーケンスの生成
無限シーケンスは、リストで表現できないデータ列をジェネレータで扱う典型例です。フィボナッチ数列のように次の値を前の状態から計算できる場合、必要な個数だけnext()で取り出す形にできます。
結果: 期待される出力は、フィボナッチ数列の先頭10個が1行ずつ表示される形です。
結果: 期待される表示例として、0から34までの値が順に並びます。
このとき、aとbは各反復で更新され、次の呼び出しに引き継がれます。無限ジェネレータは、呼び出し側がrange(10)のように上限を決めることで安全に扱えますし、ここがポイントです。上限が曖昧な処理では、itertools.islice()などの標準ライブラリを組み合わせる設計も候補になるのが基本です。
サンプルコード5:ジェネレータを使ったデータパイプライン
これらの性質をつなげると、入力、変換、抽出を段階的に流すパイプラインになります。numbers()で数値を作り、square()で2乗し、even()で偶数だけを残す構成です。各関数はイテレータを受け取り、別のイテレータを返します。
結果: 期待される出力は、0から9までを2乗した値のうち偶数だけが表示される形です。
結果: 期待される表示例として、0、4、16、36、64が並びます。
その設計では、途中の全件リストが作られないため、入力件数が増えてもメモリ消費を抑えやすくなります。コード最適化としては、全処理を一つの巨大な関数へ詰め込むより、変換単位を小さなジェネレータへ分けるほうがテストもしやすくなるのが基本です。性能向上だけでなく、読みやすさにも影響するのが目安です。
yield文の高度な応用例
yieldは値を外へ返すだけでなく、外部から値を受け取るコルーチン的な使い方や、ファイルを1行ずつ処理するストリーミング処理にも使えます。ただし、非同期処理のasyncやawaitとは別物です。Pythonのジェネレータは同期的な反復処理を組み立てる道具として理解すると、適用範囲を誤りにくくなります。
サンプルコード6:コルーチンとしてのyield
コルーチン風の使い方では、send()で外部から値を渡し、yieldの位置で受け取りますが、これは押さえたい点です。次のaverager()は、受け取った数値を合計しながら平均値を返す例です。初回はnext()でyield位置まで進める必要があります。
結果: 期待される出力は、送信した値をもとにした平均値として10、20、30が表示される形です。
このパターンでは、term = yield averageの左右で意味が分かれます。右側のaverageは呼び出し元へ返す値で、左側のtermは次のsend()で受け取る値です。ただし、初期化前にsend(10)のような非None値を送るとエラーになるため、開始手順を関数で包む設計も考えられます。
サンプルコード7:メモリ効率の良いデータ処理
ファイル処理では、open()で得られるファイルオブジェクト自体が行単位のイテレータとして扱えますし、これが一つの目安です。そこにyieldを組み合わせると、条件に合う行だけを呼び出し元へ流せます。大きなファイルを対象にしたコード最適化では、全行をreadlines()で読み込まない設計が基本になるのがポイントです。
結果: 期待される出力は、large_file.txt内でImportant:から始まる行だけが表示される形です。ファイルが存在しない場合はFileNotFoundErrorになります。
この例では、with文によりファイルクローズも管理されます。一方、返されたジェネレータを途中で消費しないまま保持すると、ファイルハンドルの寿命が長くなる場合があるのが目安です。必要な範囲で早めに消費する、または呼び出し側でスコープを明確にすることが安定した運用につながりますが、覚えておくと役立つでしょう。
ジェネレータの性能とメモリ利用
ジェネレータによる性能向上は、常に処理速度が上がるという意味ではありません。大きな差が出やすいのは、全件を保持する必要がないデータ処理、途中で打ち切れる検索、ファイルやAPIのストリーミング処理です。小さなデータではリストのほうが単純で読みやすい場合もあるため、入力規模と再利用の有無で判断します。
time.time()やsys.getsizeof()を使った例は、環境により数値が変わります。以下の数値は期待される出力例であり、固定の測定値ではありません。サンプルコード8:リストとジェネレータの比較
リスト内包表記は全要素を即座に作りますが、ジェネレータ関数はオブジェクトを作った時点では各要素を計算しません。次のコードでは、平方数のリストとジェネレータオブジェクトを作り、作成時間とサイズを比較するのがポイントです。厳密な性能評価にはtimeitなどが向きますが、遅延生成の違いを把握する材料になります。
結果: 期待される出力は、リスト作成にかかった時間、ジェネレータオブジェクト作成にかかった時間、それぞれのsys.getsizeof()値が表示される形です。
結果: 期待される表示例では、リストは多数の要素を保持するためサイズが大きく、ジェネレータは状態を持つオブジェクトとして小さく見えます。
ただし、この比較ではジェネレータの各要素を最後まで消費していません。そのため、ジェネレータの作成が速く見えるのは、計算を先送りしているからです。性能向上を判断するには、最終的に全要素を処理するのか、途中で打ち切るのか、結果を再利用するのかを含めて考える必要があります。
サンプルコード9:大規模データの処理
ファイルから条件に合う行を抜き出す処理では、リスト版は該当行を全てメモリへ保持するのが一般的です。ジェネレータ版は該当行を見つけるたびに返すため、呼び出し側が逐次処理するならメモリ使用量を抑えやすくなります。これは大規模ログやCSVのフィルタリングで使いやすい考え方です。
結果: 期待される出力は、リスト版とジェネレータ版の作成時間が表示される形です。timeを使うため、サンプル全体ではimport timeが必要です。
結果: 期待される表示例では、ジェネレータ版の作成時間が短く見えますが、値の消費タイミングが後ろへ移る点に注意が必要です。
一方、抽出結果を何度も走査したり、件数をすぐに知りたい場合は、リスト化したほうが後続処理を書きやすくなります。ジェネレータは一度消費すると同じオブジェクトを再利用できないため、再走査が必要なら関数を呼び直すか、必要な範囲でlist()へ変換します。性能向上だけを見ず、後続処理の形と合わせて選ぶのが現実的です。
よくあるエラーと対処法
初心者がつまずきやすいのは、ジェネレータが「値の入った箱」ではなく「値を取り出す流れ」だという点です。next()で取り出し切るとStopIterationが発生し、returnを書くとそこで終了するのが現実的です。さらに、無限ジェネレータでは呼び出し側の停止条件を忘れると処理が終わりません。
StopIteration例外の理解と対処
StopIterationは、イテレータが返せる値を使い切ったことを示す例外です。for文はこの例外を内部で処理しますが、next()を直接呼ぶ場合は呼び出し側で扱う必要があります。
結果: 期待される動きは、最初の3回で1、2、3が表示され、4回目のnext()でStopIterationが発生する形です。
その例外を利用して終了を明示的に扱うなら、tryとexceptで囲みます。通常の反復処理ではfor文に任せるほうが短く、安全に書けます。
結果: 期待される出力は、3個の値を表示したあとに終了メッセージが表示される形です。
結果: 期待される表示例として、数値のあとにジェネレータが終了しましたが続きます。
ただし、単に全件を処理したいだけなら、次のようなfor value in finite_generator()の形が読みやすくなります。StopIterationを直接処理するのは、終了時の値を拾う、または独自の反復制御が必要な場合に絞ると見通しがよくなると整理できます。
ジェネレータが返す値の管理
ジェネレータ内のreturnは、通常の関数のように呼び出し元へ直接値を返すのではなく、StopIterationのvalueに値を格納して終了します。returnの後ろにあるyieldは到達しません。
結果: 期待される動きは、1回目に1が表示され、2回目にStopIterationが発生する形です。
この終了値を取り出すには、例外オブジェクトをas eで受け取り、e.valueを参照します。終了理由を呼び出し元へ伝えたい場合に使えますが、通常のデータ列として返したい値はyieldで返すほうが自然です。
結果: 期待される出力は、1のあとに終了メッセージと返り値が表示される形です。
結果: 期待される表示例として、終了時のvalueに入った文字列がメッセージへ埋め込まれます。
この仕様を知らないと、returnの値がnext()の戻り値になると誤解しやすくなります。ジェネレータで複数値を返したいならyieldを並べ、終了時の補足情報だけをreturnに置く、と整理すると混乱を避けられますが、覚えておくと役立つでしょう。
メモリリークの回避策
ジェネレータ自体は大量の値を保持しにくい構造ですが、無制限に消費し続けるコードは別の問題を生みます。特にwhile Trueと巨大なrange()を組み合わせる処理は、終了条件や処理間隔がないとCPU時間を使い続けます。
結果: 期待される動きは、大量の回数だけ値を取り出し続ける形です。環境によっては長時間処理になり、途中停止が必要になる場合があると理解できます。
そのため、無限に値を作れる設計でも、呼び出し側で必要数を制限するか、ジェネレータ側に上限を持たせます。上限を関数の引数にすると、利用者が意図した範囲だけを安全に処理できます。
結果: 期待される出力は、0から1000までの整数が順に表示される形です。
この修正では、max_numにより生成範囲が明示されますし、ここを基本と考えるとよいでしょう。無限ジェネレータを作る場合でも、呼び出し側のbreak、islice()、タイムアウト、最大件数のいずれかを設けると、運用時のリスクを下げられます。
yield文の最適な使用例
yieldの適用先は、値を順番に処理でき、途中結果を全て保持しなくてよい処理です。外部APIのページング、複数変換のチェーン、リアルタイム風のデータ処理では、ジェネレータが処理単位を自然に分けます。一方、ランダムアクセスや何度も同じ結果を走査する処理では、リストやタプルのほうが扱いやすい場合があると覚えるとよいでしょう。
サンプルコード10:外部APIからのデータ取得
APIがoffsetとlimitでページングできる場合、1回のリクエストで全件を取得せず、バッチ単位で処理できます。fetch_data()はレスポンスのjson()を取得し、データが空になったところで終了します。
結果: 期待される動きは、APIから取得したデータを100件単位のバッチとして受け取り、各itemをprocess_item()へ渡す形です。
ただし、このままではresponse.raise_for_status()、timeout、認証ヘッダーがありません。一般に本番向けコードでは、requests.get(url, params=params, timeout=10)のようにタイムアウトを付け、HTTPエラーを明示的に処理します。ジェネレータ化はメモリ面のコード最適化に役立ちますが、通信の堅牢性は別途設計します。
サンプルコード11:複数のジェネレータのチェーン
複数のジェネレータをつなぐと、条件抽出、変換、再抽出を読みやすい単位へ分けられますし、ここがポイントです。次の例では、偶数だけを取り出し、2乗し、100より大きい値だけを出力します。各関数は小さいため、個別にテストしやすい構成です。
結果: 期待される出力は、偶数を2乗した値のうち100より大きいものだけが表示される形です。
結果: 期待される表示例として、12相当の条件を満たす144と、16相当の条件を満たす256が並びます。
このパイプラインは、入力データが大きいほどメモリ面の効果を得やすくなります。一方、処理の途中結果をデバッグしたい場合は、任意の位置でlist(generator2(generator1(data)))のように一時的に可視化できると考えられます。性能向上を狙う本番コードと、確認しやすい開発時コードを切り替えやすい点も利点です。
サンプルコード12:リアルタイムデータの処理
リアルタイム風のデータ処理では、値が継続的に届く前提でイテレータを設計できます。次の例はrandom.randint()でセンサー値を模擬し、time.sleep(1)で一定間隔を置いて辞書を返します。
結果: 期待される動きは、温度が80を超えた場合に高温警告、湿度が70を超えた場合に高湿度警告が表示される形です。無限ループのため、停止はキーボード割り込みなどで行います。
この実装は概念説明としては短くまとまっていますが、運用向けには停止フラグ、例外処理、ログ出力、バックプレッシャーの考慮が必要になります。while Trueで流れを作る場合、終了条件を外部から渡せるようにするとテストしやすくなると言えるでしょう。ストリーム処理でも、ジェネレータはデータを全保持しない設計として役立ちますし、ここを基本と考えるとよいでしょう。
まとめ
Pythonのyieldは、ジェネレータを通じて値を順次生成し、イテレータとして扱えるようにする構文です。returnが関数を終了させるのに対し、yieldは状態を保持したまま一時停止するため、ファイル処理、API取得、パイプライン処理、無限シーケンスで効果を発揮します。
ただし、ジェネレータは万能ではありません。全件を何度も走査する、インデックスで頻繁に参照する、結果を長期間キャッシュする、といった処理ではリストのほうが自然です。逆に、順番に一度だけ処理できるデータなら、コード最適化とメモリ削減を同時に狙えるため、性能向上の選択肢として検討できます。
特に押さえたいのは、StopIteration、yield from、send()、無限ループの終了条件です。これらを理解しておくと、ジェネレータを単なる構文としてではなく、データの流れを設計する道具として使えるのが基本です。Pythonで大量データや継続的な入力を扱う場面では、yieldを早めに候補へ入れると設計の幅が広がります。
関連記事
- Python初心者のための完全ガイド!アプリ化の10ステップ
- Pythonで実現!ウィンドウ操作の自動化15選
- Pythonで折れ線グラフ作成の完全ガイド10選
- 初心者必見!Pythonで表を操作するための7つの詳細ガイド
- Pythonで改行あり・なしを制御する方法と応用例10選
本文内の関連知識として、アプリ化の基礎はPython初心者のための完全ガイド、自動処理の発展例はPythonで実現するウィンドウ操作が参考になります。データ可視化へ進む場合はPythonで折れ線グラフ作成、表データの扱いはPythonで表を操作するための詳細ガイド、出力制御はPythonで改行あり・なしを制御する方法も合わせて確認できます。
※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。


