Pythonの「ジェネレータ(Generator)」は、「必要なときに、必要な分だけデータを生成する」ための特殊な関数です。
大量のデータを扱う際、メモリを節約しながら効率的に処理を行えるのが最大の特徴です。
1. ジェネレータの作り方
ジェネレータを作るには、通常の関数で return の代わりに yield を使います。これだけで、その関数は「ジェネレータ関数」になります。
Python
def count_up(n):
i = 1
while i <= n:
yield i # ここで一旦停止し、値を返す
i += 1
# ジェネレータオブジェクトの作成
gen = count_up(3)
# 値を一つずつ取り出す
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
# print(next(gen)) # これ以上データがないと StopIteration エラーが発生
2. 機能と特徴
ジェネレータの挙動は、通常の関数とは大きく異なります。
状態の保持: yield で値を返した後、関数は終了せずにその場所(状態)を保持したまま待機します。次に next() が呼ばれると、止まった場所から再開します。
遅延評価(Lazy Evaluation): 値が必要になるまで計算を行いません。
イテレータとしての性質: next() で値を呼び出せるほか、for 文などでループ処理が可能です。
3. ジェネレータを使うべき理由(メリット)
なぜ通常のリスト(配列)ではなく、ジェネレータを使うのでしょうか。それはメモリ効率にあります。
リストの場合: 100万個の数値をリストで作ると、100万個分のデータを一度にメモリ上に確保します。
ジェネレータの場合: 常にメモリ上にあるのは「今の1個」だけです。どれだけ大きなデータを扱っても、メモリ使用量が一定(極小)に保たれます。
4. 使うべき場面
実務で特に力を発揮するのは以下のようなシーンです。
巨大なログファイルの読み込み:
数GBあるログファイルを一度に読み込むとメモリがパンクします。ジェネレータなら「1行ずつ読んで処理する」ということが簡単に書けます。無限のストリーム処理:
センサーからのデータなど、終わりがわからない連続的なデータを扱う際に適しています。パイプライン処理:
「データを加工する」処理を複数のジェネレータでつなぎ合わせると、効率的なデータ処理パイプラインを構築できます。
5. おまけ:ジェネレータ式
リスト内包表記のように、一行でジェネレータを書くこともできます([] の代わりに () を使います)。
Python
# リスト内包表記(メモリを食う)
data_list = [x * 2 for x in range(1000000)]
# ジェネレータ式(メモリを食わない)
data_gen = (x * 2 for x in range(1000000))
まとめ:
yield を見たらジェネレータだと判断する。
大量のデータを扱うときは、リストよりジェネレータ。
「メモリをケチりたいとき」が使いどき。
この仕組みを理解すると、データ解析やWebスクレイピングなど、大規模なデータを扱うプログラムが格段に書けるようになりますよ。