Pythonのジェネレータ関数とyield文:メモリ効率の良い反復処理の実装

Pythonにおいて、リストのように「複数の要素を順番に取り出す」ことができるオブジェクトをイテラブルと呼びますが、その中でも特殊で強力な機能を持つのが**「ジェネレータ(Generator)」**です。

リストが全ての要素をメモリ上に一度に展開するのに対し、ジェネレータは「値を一つずつ生成しては一時停止する」という動作を繰り返します。これにより、大量のデータを扱う際でもメモリ消費を最小限に抑えることが可能です。

この記事では、yield 文を使ったジェネレータ関数の定義方法と、その実行フロー、そして無限数列の生成といった実用的な使い方について解説します。

目次

ジェネレータ関数の基本構造 (yield)

ジェネレータ関数は、通常の関数定義(def)と同じ構文を使いますが、値を返す際に return ではなく yield を使用する点が異なります。

  • return: 値を返して関数を終了します。
  • yield: 値を返して関数の処理を一時停止します。状態は保持され、次回呼び出し時はその続きから再開します。

基本的なコード例

処理の進行状況を文字列として返すジェネレータを作成します。

def step_generator():
    """
    呼び出されるたびに処理を少しずつ進めるジェネレータ
    """
    print("--- ステップ1開始 ---")
    yield "Step 1 完了"
    
    print("--- ステップ2開始 ---")
    yield "Step 2 完了"
    
    print("--- ステップ3開始 ---")
    yield "Step 3 完了"

# ジェネレータオブジェクトの生成(この時点では関数の中身は実行されません)
gen_process = step_generator()

print(f"オブジェクト: {gen_process}")

実行結果:

オブジェクト: <generator object step_generator at 0x...>

next() 関数による実行と再開

生成されたジェネレータオブジェクトから値を取り出すには、組み込み関数の next() を使用します。

next() が呼ばれると、ジェネレータ関数は次の yield 文まで処理を進め、そこで値を返して停止します。

# 1回目の next()
print(f"取得値1: {next(gen_process)}")

# 2回目の next()
print(f"取得値2: {next(gen_process)}")

# 3回目の next()
print(f"取得値3: {next(gen_process)}")

実行結果:

--- ステップ1開始 ---
取得値1: Step 1 完了
--- ステップ2開始 ---
取得値2: Step 2 完了
--- ステップ3開始 ---
取得値3: Step 3 完了

3回目の yield の後、さらにもう一度 next() を呼び出すと、これ以上 yield がないため StopIteration という例外が発生し、ジェネレータの終了が通知されます。

for ループでの利用

実務においては、next() を手動で何度も呼ぶことは稀です。ジェネレータはイテラブルであるため、リストと同じように for 文でループ処理が可能です。

for 文は内部的に StopIteration 例外を検知して自動的にループを終了するため、エラー処理を記述する必要はありません。

# 新しいジェネレータオブジェクトを生成
gen_loop = step_generator()

print("--- ループ処理開始 ---")

for step in gen_loop:
    print(f"ループ内での受け取り: {step}")

print("--- ループ処理終了 ---")

実行結果:

--- ループ処理開始 ---
--- ステップ1開始 ---
ループ内での受け取り: Step 1 完了
--- ステップ2開始 ---
ループ内での受け取り: Step 2 完了
--- ステップ3開始 ---
ループ内での受け取り: Step 3 完了
--- ループ処理終了 ---

実用例:無限シーケンス(フィボナッチ数列)

ジェネレータの「必要な時だけ値を生成する」という特性を利用すると、理論上無限に続く数列などをメモリを圧迫せずに表現できます。

例として、フィボナッチ数列(前の2つの項の和が次の項になる数列:0, 1, 1, 2, 3, 5…)を生成するジェネレータを作成します。

def fibonacci_sequence():
    """
    フィボナッチ数列を無限に生成するジェネレータ
    """
    num_a, num_b = 0, 1
    
    while True:
        # 現在の num_a を返して一時停止
        yield num_a
        
        # 次の値を計算
        num_a, num_b = num_b, num_a + num_b

# ジェネレータの生成
fib_gen = fibonacci_sequence()

print("フィボナッチ数列の先頭10個を表示:")

# 無限ループになるため、rangeで回数を制限して next() を呼ぶ
for _ in range(10):
    value = next(fib_gen)
    print(value, end=", ")

実行結果:

フィボナッチ数列の先頭10個を表示:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 

この fibonacci_sequence 関数には while True による無限ループが含まれていますが、yield で毎回停止するため、プログラムがフリーズすることはありません。必要な数だけ next() で取り出すことができます。

まとめ

  • ジェネレータは、反復処理可能なオブジェクトの一種です。
  • return の代わりに yield を使うことで、関数の状態を保持したまま一時停止・再開ができます。
  • next() 関数で次の値を取り出します。
  • forで使うのが一般的です。
  • 全データを一度にメモリに展開しないため、巨大なデータや無限の数列を扱う際に非常に効率的です。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次