Python 速習チュートリアル

Python のジェネレータ (Generators)

1. ジェネレータ (Generators) とは

Python のジェネレータ(Generator)は、イテレータ(Iterator)を作成するためのシンプルかつ強力なツールです。

通常の関数のように見えますが、値を返す際に return ステートメントの代わりに yield ステートメントを使用する点が異なります。ジェネレータは、一度にすべての結果をメモリに格納するのではなく、要求されるたびに一つずつ値を生成(Yield)するため、非常に高いメモリ効率を実現します。

2. ジェネレータの作成

ジェネレータを作成するには、関数内で yield キーワードを使用します。

2.1 yield ステートメントの使用例

以下の例は、3つの値を順番に生成するシンプルなジェネレータ関数です。

def my_generator():
    yield "最初"
    yield "次"
    yield "最後"

# ジェネレータオブジェクトを取得
gen = my_generator()

for x in gen:
    print(x)

3. next() 関数による逐次実行

ジェネレータは、next() 関数を使用して次の値を取得することができます。yield に到達すると、関数はその時点の状態(変数の値や実行位置)を保持したまま実行を一時停止し、値を呼び出し元に返します。

3.1 next() の挙動を確認する

def my_generator():
    yield 1
    yield 2
    yield 3

gen = my_generator()

print(next(gen)) # 1 を出力
print(next(gen)) # 2 を出力
print(next(gen)) # 3 を出力

# これ以上値がない状態で next() を呼ぶと StopIteration エラーが発生します

4. ジェネレータ vs 通常の関数

通常の関数とジェネレータの主な違いは以下の通りです。

  • 通常の関数: return が実行されると、関数は完全に終了し、ローカル変数の状態などはすべて破棄されます。
  • ジェネレータ: yield が実行されると、関数は値を返した後に「ポーズ(一時停止)」します。次に呼び出されたとき、停止した直後の行から実行を再開します。

4.1 ループ内でのジェネレータ

数値をカウントするジェネレータの例です。

def count_up_to(n):
    count = 1
    while count <= n:
        yield count
        count += 1

counter = count_up_to(5)
for num in counter:
    print(num)

5. ジェネレータ式 (Generator Expressions)

リスト内包表記に似た構文で、より簡潔にジェネレータを作成できるのがジェネレータ式です。リスト内包表記が角括弧 [] を使用するのに対し、ジェネレータ式は丸括弧 () を使用します。

5.1 ジェネレータ式の構文

# リスト内包表記(すべての要素を一度にメモリに確保)
my_list = [x**2 for x in range(10)]

# ジェネレータ式(必要になるまで計算しない)
my_gen = (x**2 for x in range(10))

for val in my_gen:
    print(val)

6. なぜジェネレータを使うのか?

ジェネレータを利用する主なメリットは、パフォーマンスメモリ効率です。

  1. メモリの節約: 何百万もの要素を持つリストを作成すると膨大なメモリを消費しますが、ジェネレータは一度に一つの要素しか生成しないため、メモリ消費を最小限に抑えられます。
  2. 遅延評価(Lazy Evaluation): 値が必要になるまで計算を行わないため、無限ストリームの処理や大規模なファイルの読み込みに最適です。
  3. コードの簡潔化: イテレータプロトコル(__iter____next__)を手動で実装するよりも、はるかに少ないコード量でイテレータを構築できます。