3.7 ジェネレーター — generator
Pythonの「ジェネレーター(Generator)」は、反復処理(イテレーション)を行うための仕組みの一つです。前回の「3.6 ジェネレーター式」は内包表記に似た一行の構文でしたが、今回は関数ベースでジェネレーターを定義する「ジェネレーター関数」について解説します。
ジェネレーターを理解する鍵は、yield(イールド)文にあります。
1. ジェネレーター関数と yield の基本
Section titled “1. ジェネレーター関数と yield の基本”関数の中に return の代わりに yield 文が含まれていると、その関数は自動的に「ジェネレーター関数」になります。
通常の関数は呼び出されると一気に最後まで実行され、return で値を返して終了します。しかし、ジェネレーター関数は**yield の位置で処理を「一時停止」し、呼び出し元に値を返します**。
def simple_generator(): print("最初の処理") yield 1
print("2番目の処理") yield 2
print("最後の処理") yield 3
# ジェネレーターオブジェクトを生成gen = simple_generator()print(gen) # <generator object simple_generator at 0x...>ジェネレーター関数を呼び出しただけでは、関数の中身は実行されません。「ジェネレーターオブジェクト」が返されるだけです。
2. next() 関数で処理を進める
Section titled “2. next() 関数で処理を進める”ジェネレーターの処理を進めるには、組み込み関数の next() を使用するか、for ループに渡します。
# next()を呼ぶたびに、次のyieldまで進んで値を返すprint(next(gen))# 出力: 最初の処理# 出力: 1
print(next(gen))# 出力: 2番目の処理# 出力: 2
print(next(gen))# 出力: 最後の処理# 出力: 3
# これ以上yieldがないのにnext()を呼ぶとエラーになる# print(next(gen)) # StopIteration エラーが発生for ループを使うと、StopIteration エラーを自動的にキャッチしてループを終了してくれるため安全で簡単です。
for value in simple_generator(): print(f"受け取った値: {value}")3. なぜジェネレーターを使うのか?(メリット)
Section titled “3. なぜジェネレーターを使うのか?(メリット)”最大のメリットは「状態の保持」と「メモリの節約(遅延評価)」です。
通常の関数では、処理が終わるとローカル変数はすべて破棄されます。しかしジェネレーターは、yield で一時停止している間も、ローカル変数などの「状態」を保持し続けます。
また、数百万件のログファイルを行ごとに読み込んで処理する場合など、すべてのデータを一度にリスト(メモリ)に読み込むとメモリ不足でプログラムがクラッシュする可能性があります。ジェネレーターを使えば、1行ずつ読み込んで処理し、次の行が要求されるまで待機するため、メモリ消費を劇的に抑えることができます。
# メモリに優しい無限カウントジェネレーターdef infinite_counter(): count = 0 while True: yield count count += 1 # 状態を保持したまま次回のnext()でここから再開
counter = infinite_counter()print(next(counter)) # 0print(next(counter)) # 14. yield from:別のジェネレーターへの処理の委譲
Section titled “4. yield from:別のジェネレーターへの処理の委譲”Python 3.3以降では、yield from 構文が追加されました。これを使うと、別のジェネレーターやイテラブル(リストなど)の要素を順番に yield する処理を、非常に簡潔に書くことができます。
従来の書き方 (forループを使用)
Section titled “従来の書き方 (forループを使用)”def sub_generator(): yield "A" yield "B"
def main_generator(): yield 1 # サブジェネレーターから1つずつ取り出してyieldする for item in sub_generator(): yield item yield 2
print(list(main_generator())) # [1, 'A', 'B', 2]yield from を使った書き方
Section titled “yield from を使った書き方”def main_generator_clean(): yield 1 # サブジェネレーターの要素をそのまま外に流す(委譲する) yield from sub_generator() yield 2
print(list(main_generator_clean())) # [1, 'A', 'B', 2]yield from を使うことで、ネストしたジェネレーター同士の連携や、リストなどのイテラブルからの値の連続送出を、for ループを書かずにスッキリと記述できます。