概要(yieldは「一つ返して一時停止、次に再開」する魔法のスイッチ)
yieldは、関数を“ジェネレーター”に変えるキーワードです。yieldに到達すると値を一つ返して処理を一時停止し、次に呼ばれたときはその直後から続きが動きます。全データを一気に作らず「必要になった分だけ」順に生成するため、メモリ効率が高く、巨大データ・ファイル読取り・ページングAPIと最高の相性です。
基本構文(yieldで作るジェネレーター関数)
もっとも小さな例(順に値を返す)
def count_up(n: int):
for i in range(n):
yield i # 一つ返す → 停止 → 次に再開
g = count_up(3)
print(next(g)) # 0
print(next(g)) # 1
print(next(g)) # 2
# 次のnextでStopIteration(尽きた合図)
Pythonyieldは「値を返して止まる」合図です。関数は終了せず、次のnext()で前回のyield直後から再開します。forは内部でnext()を繰り返し、尽きたら自動で止まります。
ジェネレーター式(丸括弧で“逐次生成”)
squares = (x * x for x in range(5))
print(list(squares)) # [0, 1, 4, 9, 16]
Pythonリスト内包に似ていますが、結果をリスト化せず「必要時に一つずつ」作ります。大きなデータでもメモリが膨らみません。
重要ポイントの深掘り(returnとの違い・遅延生成・終了)
returnとの違い(一括 vs 逐次)
- returnはその場で関数を終わらせ、結果を一括で返します。
- yieldは一つ返したら“停止”し、次回は続きから再開します。返すたびに動くので「逐次生成」になります。
遅延生成(必要なときだけ作る)
yieldは値が要求されたタイミングで作るため、100万件でも「今必要な1件」しかメモリに載せません。ストリームやページングのような“終わりが遠い”処理を安全に扱えます。
終了はStopIteration(自然に終わる)
ジェネレーター関数はループを抜ける、もしくはreturnすると自動で終了し、next()はStopIterationを受け取って止まります。明示的にStopIterationを投げる必要は通常ありません。
実践での使い方(ファイル・API・パイプライン)
巨大ファイルを安全に逐次処理
def non_empty_lines(path: str, encoding: str = "utf-8"):
with open(path, "r", encoding=encoding) as f:
for line in f:
s = line.rstrip("\n")
if s:
yield s
for line in non_empty_lines("data.txt"):
print(line)
Python一行ずつ処理するため、ファイルが大きくてもメモリは一定です。withに閉じ込めることでリソース解放も自動化できます。
ページングAPIを“forで全部”扱う
import requests
def users(base_url: str, timeout: float = 5.0):
page = 1
while True:
r = requests.get(f"{base_url}/users", params={"page": page}, timeout=timeout)
r.raise_for_status()
items = r.json().get("items", [])
if not items:
return
for u in items:
yield {"id": str(u.get("id", "")), "name": u.get("name") or "unknown"}
page += 1
Python呼び手は「全ユーザー」をforで自然に処理できます。内部ではページ単位で取得するので、メモリ効率が高く、落ちにくいです。
段階的な変換をつなぐ(パイプライン)
def normalized_users(src):
for u in src:
yield {"id": u["id"], "name": u["name"].strip()}
for u in normalized_users(users("https://api.example.com")):
print(u)
Pythonジェネレーター同士をつなぐと、責務が分かれ、拡張やテストが簡単になります。
設計のコツ(副作用・パフォーマンス・再利用性)
副作用は小さく、予測可能に
yieldの間に重いI/Oや複雑な副作用を入れすぎると、1ステップの遅延や失敗が読めなくなります。読み取り・軽い整形はジェネレーター、書き込みや接続は明示メソッドへ分離すると安全です。
速度とピークメモリをバランスする
- まとめ取得+逐次yield(“バッチ化”)でAPIやDBの往復を減らし、ピークメモリを一定に保ちます。
def batched(fetch_fn, batch_size: int = 100):
offset = 0
while True:
batch = fetch_fn(offset, batch_size)
if not batch:
return
for item in batch:
yield item
offset += len(batch)
Python再利用性(いつでももう一度回せる形)
関数ジェネレーターは毎回“新しい”イテレータが返るため、何度でも回せます。自作クラスに反復を持たせたいときは、iter内でyieldを使うと同じ利点が得られます。
応用(yield from・無限列・制御メソッド)
yield fromで委譲を簡潔に
def flatten(list_of_lists):
for xs in list_of_lists:
yield from xs # 内側のイテラブルをそのまま流す
Pythonネストを平らにして、可読性を上げます。
無限ジェネレーターの安全運用
def naturals():
n = 0
while True:
yield n
n += 1
for i in naturals():
if i > 100:
break # 呼び手側で境界を付ける
Python“終わらない”ことは有用ですが、呼び手で上限やbreak条件を必ず設けます。
制御メソッド(必要になったら)
- g.close(): 早期終了
- g.send(value): 次のyieldへ値を注入
- g.throw(exc): ジェネレーター内に例外を投げ込む まずは基本のfor/nextで十分。高度な制御は必要になってから。
例題(ログを安全に要約して流すジェネレーター)
まとめて読み、短く整えて逐次処理
def summarized_logs(read_fn, batch_size: int = 1000):
"""
read_fn(offset, batch_size) -> list[str]
を想定。長い行は先頭100文字に短縮して流す。
"""
offset = 0
while True:
batch = read_fn(offset, batch_size)
if not batch:
return
for line in batch:
s = line.strip()
yield s[:100] if len(s) > 100 else s
offset += len(batch)
Pythonピークメモリを一定に、処理の歩幅を軽く、呼び手側はforで自然に消費できます。
まとめ(yieldは「逐次・遅延・安全」な処理を作る最短ルート)
yieldは、関数を“必要な分だけ値を生む”ジェネレーターに変えるスイッチです。returnの“一括”に対して、yieldは“逐次”で、メモリ効率とスケーラビリティが段違い。巨大ファイル、ページングAPI、段階的変換、無限列まで、落ちずに拡張できる。副作用は小さく、バッチ化やパイプラインで歩幅を整える。この型を体に入れれば、初心者でも「速く、軽く、読みやすい」反復処理が自然に書けるようになります。
