Skip to content

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...>

ジェネレーター関数を呼び出しただけでは、関数の中身は実行されません。「ジェネレーターオブジェクト」が返されるだけです。

ジェネレーターの処理を進めるには、組み込み関数の 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)) # 0
print(next(counter)) # 1

4. 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]
def main_generator_clean():
yield 1
# サブジェネレーターの要素をそのまま外に流す(委譲する)
yield from sub_generator()
yield 2
print(list(main_generator_clean())) # [1, 'A', 'B', 2]

yield from を使うことで、ネストしたジェネレーター同士の連携や、リストなどのイテラブルからの値の連続送出を、for ループを書かずにスッキリと記述できます。