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
文で繰り返し処理できる- リスト内包表記のようにジェネレーター式も使える
ジェネレーターは「遅延評価で値を生み出す仕組み」と理解すると応用しやすくなります。
補足事項など
return
と yield
の違い
「return
の代わりに yield
を使う」と考えても大体OKですが、return
と yield
には重要な違いがいくつかあります。
- 処理の流れ
return
は関数を即座に終了させて値を返します。yield
は「その時点の値を返して一時停止」し、次に呼ばれたときに続きから再開します。
- 返されるもの
return
を含む関数を呼ぶと「値そのもの」が返ります。yield
を含む関数を呼ぶと「ジェネレーターオブジェクト」が返り、それを通じて値を順に取り出せます。
- 終了の仕方
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オブジェクトと似ている
共通点
- 逐次的にデータを取り出す
データベースのCursor
もfetchone()
や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 は弱く、実際には「イルド」に近く聞こえます。
なので日本語で読むなら「イールド」と表記するのが一番近い感じです。