はじめに
Python を学んでいると、ある段階で必ず出会うのが 生成器(Generator) と yield です
def func():
yield 1
多くの人がここでこう思います。
-
yieldってreturnと何が違うの? - 関数なのに途中で止まるってどういうこと?
- ジェネレータはいつ使うのが正解?
ジェネレータは 文法のトリック ではありません。
これは Python の 設計思想を体現した重要な仕組み です。
本記事では、ジェネレータを 概念 → 仕組み → 実践 の順で解説します。
1. ジェネレータ(Generator)とは何か
一言で言うと
ジェネレータとは「値を1つずつ生成する関数」
より正確には:
yieldを含む関数を呼び出すと、イテレータが返る
2. ジェネレータ関数の基本
2.1 最もシンプルなジェネレータ
def gen():
yield 1
yield 2
yield 3
呼び出してみます。
g = gen()
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3
print(next(g)) # StopIteration
ここで重要なのは:
-
gen()を呼んでも 処理は実行されない - ジェネレータオブジェクトが返るだけ
3. yield と return の決定的な違い
3.1 return の挙動
def func():
return 1
return 2
-
returnが実行された瞬間に関数は終了 - 値は1つしか返せない
3.2 yield の挙動
def gen():
yield 1
yield 2
-
yieldは 値を返して処理を一時停止 - 次の
next()で 続きから再開
生成器は「状態を保存する関数」
4. 実行の流れを可視化する
def demo():
print("A")
yield 1
print("B")
yield 2
print("C")
g = demo()
next(g)
# A
# -> 1
next(g)
# B
# -> 2
next(g)
# C
# StopIteration
ポイント
- 関数のローカル変数
- 実行位置
- すべてが保持される
5. ジェネレータはイテレータである
g = gen()
iter(g) is g # True
ジェネレータは以下を満たします:
-
__iter__()を持つ -
__next__()を持つ
つまり:
ジェネレータ ⊂ イテレータ
6. ジェネレータ式(Generator Expression)
6.1 基本形
gen = (x * x for x in range(5))
対比:
lst = [x * x for x in range(5)]
| 項目 | リスト内包表記 | ジェネレータ |
|---|---|---|
| 評価タイミング | 即時 | 遅延 |
| メモリ使用 | 多い | 少ない |
| 再利用 | 可能 | 不可 |
7. なぜ生成器が重要なのか
7.1 メモリ効率が圧倒的に良い
def count_up(n):
for i in range(n):
yield i
for x in count_up(10**9):
...
- 10億個のデータを保持しない
- 必要な分だけ生成
7.2 ファイル・ログ処理
def read_lines(path):
with open(path) as f:
for line in f:
yield line.strip()
- 大容量ログでも安全
- ストリーム処理が自然に書ける
7.3 パイプライン処理(Pythonic)
def numbers():
for i in range(10):
yield i
def even(nums):
for n in nums:
if n % 2 == 0:
yield n
def square(nums):
for n in nums:
yield n * n
for x in square(even(numbers())):
print(x)
データを「流す」設計
8. よくある落とし穴
8.1 生成器は一度しか使えない
g = (x for x in range(3))
list(g) # [0, 1, 2]
list(g) # []
8.2 return は即終了
def gen():
yield 1
return 2
-
returnは生成器を終了させる -
2はStopIteration(2)に入る(通常使わない)
9. for 文と生成器の関係
for x in gen():
print(x)
内部的には:
it = iter(gen())
while True:
try:
x = next(it)
print(x)
except StopIteration:
break
ジェネレータは for 文の主役
10. いつジェネレータを使うべきか
ジェネレータが向いている場面
- データ量が大きい
- 一度しか使わない
- ファイル・ネットワーク・ストリーム
- パフォーマンス・省メモリ重視
向いていない場面
- ランダムアクセスが必要
- 複数回使い回す
- 小規模データ
おわりに
生成器は Python における「流れる思考」そのもの
- データを溜めない
- 必要な分だけ作る
- 状態を自動で管理する
ジェネレータを自然に使えるようになると、
Python のコードは 短く・速く・美しく なります。