Pythonのジェネレーターとはどんな時に役立つ?

Pythonのジェネレーターは「イテレータを簡単に書ける仕組み」で、大量データ処理や逐次的な処理を効率的に行いたいときに役立ちます。主に以下のような場面で効果を発揮します。

Pythonのジェネレーターが役立つ場面

メモリ効率が必要なとき

通常のリストはすべての要素を一度にメモリに展開しますが、ジェネレーターは「必要になった時点」で値を一つずつ生成します。例えば1億個の数列を扱う場合でも、ジェネレーターならメモリをほとんど消費せずに処理できます。

無限列や終了条件のないデータを扱うとき

例えば「ずっとフィボナッチ数列を計算し続ける」といった処理もジェネレーターなら可能です。リストでは事前に全て計算できませんが、ジェネレーターは必要な分だけ取り出せます。

大規模ファイルやストリームを逐次処理するとき

ログファイルの行を一行ずつ処理する場合、すべてを読み込む必要はなく、ジェネレーターを使えば「読み込み→処理→次へ」と流れるように処理できます。これにより大容量データでも効率的に扱えます。

可読性や簡潔さを上げたいとき

__iter____next__ を実装するクラスをわざわざ書かなくても、yield を使うだけでイテレータが作れます。そのためコードが短く明快になります。

まとめ

  • メモリを節約して大量データを扱いたいとき
  • 無限列や逐次的な処理が必要なとき
  • ファイルやストリームを行単位で処理したいとき
  • シンプルにイテレータを実装したいとき

こうした状況でジェネレーターは非常に有用です。

基本的な使い方

Pythonのジェネレーターの基本的な使い方は「関数の中で yield を使う」ことです。通常の関数は return で値を返すとそこで終了しますが、ジェネレーター関数は yield で一時停止し、次に呼ばれると続きから再開します。

基本構文

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() を呼ぶたびに yield の位置まで進み、その値を返します。最後まで行くと StopIteration という例外で終了を知らせます。

for文で使う

ジェネレーターはイテレータなので、for 文で簡単に回せます。

for value in my_generator():
    print(value)

# 出力:
# 1
# 2
# 3

実用的な例:数列を作る

例えば 0 から n-1 までの数を順番に返すジェネレーターは次のように書けます。

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

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

# 出力:
# 0
# 1
# 2
# 3
# 4

ジェネレーター式

リスト内包表記に似た形でも書けます。

gen = (x * x for x in range(5))
for value in gen:
    print(value)

# 出力:
# 0
# 1
# 4
# 9
# 16

これも「必要になったときに値を取り出す」仕組みになっています。

まとめ

  • yield を使うと関数がジェネレーターになる
  • next() で1つずつ取り出せる
  • for 文で繰り返し処理できる
  • リスト内包表記のようにジェネレーター式も使える

ジェネレーターは「遅延評価で値を生み出す仕組み」と理解すると応用しやすくなります。

補足事項など

returnyield の違い

return の代わりに yieldを使う」と考えても大体OKですが、returnyield には重要な違いがいくつかあります。

  1. 処理の流れ
    • return は関数を即座に終了させて値を返します。
    • yield は「その時点の値を返して一時停止」し、次に呼ばれたときに続きから再開します。
  2. 返されるもの
    • return を含む関数を呼ぶと「値そのもの」が返ります。
    • yield を含む関数を呼ぶと「ジェネレーターオブジェクト」が返り、それを通じて値を順に取り出せます。
  3. 終了の仕方
    • return は値を返したら終了。
    • yield は何度も使えるが、最終的には関数の終わりに到達すると StopIteration が発生して終了。
    • またジェネレーター内で return を書くと、その時点で StopIteration が発生し、返した値は終了理由として扱われます(通常の戻り値とは扱いが違います)。

簡単な比較

def with_return():
    return 1
    return 2  # ここには到達しない

def with_yield():
    yield 1
    yield 2

print(with_return())   # -> 1
print(list(with_yield()))  # -> [1, 2]

まとめ

  • 「関数を終了する」のが return
  • 「値を返して処理を中断・再開する」のが yield

なので正確には「yield は関数を終了せずに一時停止する特別な return」と捉えると分かりやすいです。

一般的なデータベースのCursorオブジェクトと似ている

共通点

  • 逐次的にデータを取り出す
    データベースの Cursorfetchone()fetchmany() で1件ずつ(あるいはまとめて)結果を取り出しますよね。ジェネレーターも next() で1つずつ値を取り出す仕組みです。
  • 全件を一度に展開しない
    Cursor が巨大な結果セットを一気にメモリに載せず、必要になったときに順番に返すように、ジェネレーターも遅延評価で値を生成します。
  • イテレータとして扱える
    どちらも for 文で自然に繰り返し処理できます。for row in cursor: のように書けるのと同じで、ジェネレーターも for value in generator: と書けます。

違い

  • データの出どころ
    Cursor はデータベースに問い合わせた結果を返す「外部データの入り口」です。
    ジェネレーターは自分で定義したロジックで値を「その場で生成」します。
  • 再利用性
    多くの Cursor は使い切ったら再利用できません。同様にジェネレーターも一度消費すると再び最初から繰り返すことはできません(再生成が必要)。

まとめ

ジェネレーターは「データベースの Cursor によく似た仕組みをPythonコードで自由に作れるもの」と考えると理解しやすいです。
違いは「Cursor=外部のデータソース」「ジェネレーター=自作のデータ生成器」という点ですね。

yieldの発音は「イールド」

英語では [jiːld] と発音されます。

  • 最初の y は「イ」に近い音(yes の y と同じ)。
  • iea の部分は「イー」。
  • ld の部分は「ルド」ですが、d は弱く、実際には「イルド」に近く聞こえます。

なので日本語で読むなら「イールド」と表記するのが一番近い感じです。

-Python