概要(ジェネレーターは「必要な分だけ、順に値を生む」仕組み)
ジェネレーターは、yieldを使って「次の値」を一つずつ返し、呼び出しのたびに前回の続きから再開できる関数(またはオブジェクト)です。全データを一度に作らず“遅延生成”するため、メモリ効率が高く、大量データやストリーム処理、ページングAPIに相性抜群です。forで自然に回せて、尽きれば自動で止まるのも魅力です。
基本構文(ジェネレーター関数とジェネレーター式)
ジェネレーター関数(yieldで一時停止→再開)
def count_up(n: int):
for i in range(n):
yield i # 0, 1, ... n-1 を順に返す
g = count_up(3)
print(next(g)) # 0
print(next(g)) # 1
print(next(g)) # 2
# 次でStopIteration(尽きた合図)
Pythonyieldは「一つ返して一時停止」の合図です。次に呼ばれると、yieldの直後から処理が再開されます。returnは終了、yieldは継続前提の“一時停止”だと覚えると理解が速いです。
ジェネレーター式(丸括弧で“逐次生成”)
gen = (x * x for x in range(5)) # 0,1,4,9,16 を逐次生成
print(list(gen)) # [0, 1, 4, 9, 16]
Pythonリスト内包表記に似ていますが、結果をリストにせず「一つずつ生成するジェネレーター」を返します。大きなデータでもメモリを圧迫しません。
重要ポイントの深掘り(遅延評価・メモリ効率・終了の扱い)
遅延評価でメモリ節約
ジェネレーターは「必要になった時点」で次の値を作るため、100万件でも一度に保持しません。巨大ファイル、APIページング、ストリーム変換などで“落ちない”実装ができます。
終了は自然(StopIteration)
forは内部でnext()を繰り返し、値が尽きるとStopIterationでループを止めます。ジェネレーター関数では、ループを抜ける(またはreturnする)だけで自動的に終了します。
速度と副作用のバランス
重いI/Oや複雑な副作用をyield間に詰め込み過ぎると、1ステップの体感が悪化します。読み取りや軽い整形はジェネレーターに、書き込み・接続は明示メソッドに分離すると予測可能で安全です。
実践例(ファイル・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で自然に処理できる(常に数行しかメモリを使わない)
for line in non_empty_lines("data.txt"):
print(line)
Pythonwithでクリーンアップを自動化しつつ、行ごとに必要な分だけ処理します。
ページング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 from・ジェネレーター制御・無限列)
yield from(委譲でコードを簡潔に)
def flatten(list_of_lists):
for xs in list_of_lists:
yield from xs # 内側のイテラブルをそのまま流す
Pythonネストしたループを平らにし、読みやすく保てます。
制御メソッド(close/send/throw)を知っておく
ジェネレーターは、g.close()で早期終了、g.send(value)で次のyieldへ値を投げる、g.throw(exc)で例外を投げ込むなどの制御が可能です。まずは基本のfor/nextに慣れ、必要になったら段階的に使いましょう。
無限ジェネレーターの扱い
def naturals():
n = 0
while True:
yield n
n += 1
# 利用側で上限やbreakを必ず設ける
for i in naturals():
if i > 100:
break
print(i)
Python“終わらない”こと自体は有用ですが、呼び手側で安全な境界を必ず設けます。
例題(バッチ取得+逐次出力で“重い処理を軽く”する)
まとめて取りつつ、1件ずつ流す
def batched(fetch_fn, batch_size: int = 100):
offset = 0
while True:
batch = fetch_fn(offset, batch_size) # e.g., DBからまとめ取り
if not batch:
return
for item in batch:
yield item
offset += len(batch)
# 呼び手はforで逐次処理できる(ピークメモリが一定)
Pythonピークメモリを抑え、処理全体の体感を滑らかにします。
まとめ(ジェネレーターは“遅延生成”でスケールする最強の基本形)
ジェネレーターは、yieldで“必要な分だけ順に値を生む”仕組みです。メモリ効率が高く、終了は自動、forで自然に扱える。巨大ファイル、ページングAPI、段階的変換、無限列まで、スケールと保守性を両立できます。まずはジェネレーター関数と式に慣れ、必要に応じてyield fromや制御メソッドへ広げていく。これを体に入れれば、初心者でも「落ちない・速い・読みやすい」反復処理を自在に設計できます。
