概要
大規模なデータ処理や、無限系列、重いI/O処理の最適化において、
「今必要な分だけを評価する」=遅延評価(lazy evaluation) は極めて重要な設計原則である。
Pythonにおける遅延評価の中核が yield
とジェネレータ。
本稿ではその構文、実行原理、パフォーマンス面の利点、
さらに設計上の美学としての活用法を解説する。
1. ジェネレータとは?
yield
を使って値を「返しつつ一時停止する」関数=ジェネレータ関数
def count_up_to(n):
i = 1
while i <= n:
yield i
i += 1
for num in count_up_to(3):
print(num)
出力:
1
2
3
- 関数内に
yield
が含まれると、その関数は「ジェネレータオブジェクト」を返す - 実行は1回ごとに一時停止し、再開されるたびに次の
yield
へ進む
2. 遅延評価のメリット
✅ メモリ効率が高い
def read_large_file(path):
with open(path) as f:
for line in f:
yield line
→ ファイル全体を一気に読み込まず、1行ずつ処理可能
✅ 無限列にも対応できる
def infinite():
i = 0
while True:
yield i
i += 1
→ 通常のリストでは不可能な 無限シーケンスの扱い を安全に実現できる
3. ジェネレータ式(generator expression)
squares = (x**2 for x in range(5))
print(next(squares)) # → 0
-
[]
ではなく()
を使うことで リスト内包表記の遅延版 を生成 - mapやfilterとの併用も有効
result = sum(x for x in range(1_000_000) if x % 2 == 0)
→ 不要なリスト化を避けつつ、大規模計算を効率化
4. yield from
による委譲
def subgen():
yield 1
yield 2
def main():
yield 0
yield from subgen()
yield 3
print(list(main())) # → [0, 1, 2, 3]
- ジェネレータ同士の合成・中継に便利
- ネストを避けて可読性が向上する
5. 状態付き処理・I/Oへの応用
def chunked(iterable, size):
chunk = []
for item in iterable:
chunk.append(item)
if len(chunk) == size:
yield chunk
chunk = []
if chunk:
yield chunk
→ 状態(バッファ)を保持しつつ、チャンク単位で処理
→ DBのバルク挿入やバッチ処理に最適
6. 実務での代表パターン
✅ ストリーム系の逐次処理
def parse_lines(lines):
for line in lines:
if line.startswith("#"):
continue
yield line.strip()
✅ メモリ使用量を抑えたデータフィルタリング
def filter_valid(items):
return (item for item in items if item.is_valid())
✅ ジェネレータを for
だけでなく list()
にも渡せる
data = list(chunked(range(10), 3))
# → [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
よくある誤解と対策
❌ yield
は戻り値を返すもの?
→ ✅ yield
は「返して止まる」、return
は「返して終了」
→ 完全に異なる設計意図を持つ
❌ ジェネレータは遅い?
→ ✅ 計算量が重ければむしろジェネレータの方が高速になる
→ イテレーション全体を保持しない分、オーバーヘッドが小さい
❌ yield
を使うと複雑になる
→ ✅ 小さな関数単位で切り出すと状態管理が明示的になり、ロジックが明快になる
結語
yield
やジェネレータ式は、Pythonにおける**“遅延評価による設計効率化”の本質を体現する機能**である。
- メモリ効率・I/O最適化・状態付き逐次処理に強く
- 「処理の完了」ではなく「処理の一時停止」という発想で設計できる
- コードの柔軟性と再利用性を高める設計原理
Pythonicとは、“すべてを保持せず、必要なときに必要な分だけを処理する美しさ”であり、
ジェネレータはそれを最も象徴的に実現する構文である。