Pythonのジェネレーターをやさしく解説!初心者でもわかるyieldとイテレーターの基本
生徒
「Pythonのジェネレーターって聞いたんですが、なんですか?難しそうです…」
先生
「ジェネレーターは、大量のデータを一つずつ順番に取り出せる便利なしくみですよ。使い方も簡単なんです。」
生徒
「それってfor文と何が違うんですか?」
先生
「とても良い質問です。for文で全部まとめて処理する代わりに、ジェネレーターでは一つずつ処理できるんです。少しずつ一緒に学んでいきましょう!」
1. ジェネレーターとは?Pythonのyieldの意味を解説
Python(パイソン)には、ジェネレーター(generator)という特別な関数があります。これは、一度に全部の結果を出すのではなく、必要なときに一つずつ値を返す関数です。
普通の関数ではreturnで結果を返しますが、ジェネレーターではyield(イールド)を使います。
yieldは「一時停止して、次に呼ばれたときにまた再開する」という動き方をします。
2. ジェネレーターを使うと何が便利なの?
- 大量のデータを一気にメモリに読み込まない
- データを一つずつ処理できるから軽くて速い
- for文と似たように書けるので初心者にも扱いやすい
たとえば100万件のデータを全部リストにすると、パソコンのメモリがいっぱいになります。でも、ジェネレーターなら1つずつ処理するので、省メモリで安全です。
3. 簡単なジェネレーター関数を作ってみよう
数字を1から3まで順番に返す簡単なジェネレーターを書いてみましょう。
def number_generator():
yield 1
yield 2
yield 3
このnumber_generator関数は、呼ばれるたびにyieldで値を1つずつ返します。次に、どうやって呼び出すかを見てみましょう。
4. ジェネレーターの使い方(呼び出し方)
ジェネレーター関数を使うには、next()関数を使います。
gen = number_generator()
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
1
2
3
このように、next()を呼ぶたびにyieldが1つずつ実行されます。
そして最後まで行くとStopIterationというエラーが出ます。これは「もう返すものがありませんよ」というサインです。
5. for文でジェネレーターを使う
for文でもジェネレーターを簡単に使えます。次のように書くと、全部順番に取り出してくれます。
for num in number_generator():
print(num)
1
2
3
初心者でも書きやすく、見た目もスッキリしています。
6. イテレーターとの違いと関係
Pythonでは、イテレーター(iterator)という「順番に取り出せるもの」があります。リストや文字列もイテレーターにできます。
ジェネレーターは、イテレーターを作る簡単な方法です。次のように言えます:
- イテレーター:順番に取り出せるもの(例:リスト)
- ジェネレーター:自分で作れるイテレーター(
yieldを使う)
実際にジェネレーター関数から作られたものは、イテレーターの仲間です。
7. ジェネレーターの中にループを書くとどうなる?
yieldは、ループと一緒に使うこともできます。たとえば、1から5までの数字を順番に返したいときはこう書きます。
def count_up():
for i in range(1, 6):
yield i
for num in count_up():
print(num)
1
2
3
4
5
このように、forの中でyieldを使えば、もっと便利に繰り返しができます。
8. ジェネレーターとリストの違いを比べてみよう
次のように、ジェネレーターとリストの違いを比べてみましょう。
# リストの場合
numbers = [i for i in range(1000000)]
# ジェネレーターの場合
def generate_numbers():
for i in range(1000000):
yield i
リストは最初からすべて作っておくので、メモリをたくさん使います。一方、ジェネレーターは必要な分だけ作るので、メモリを節約できます。
9. ジェネレーターの注意点
- 一度使い切ると、もう一度使えない
returnではなく、yieldを使うyieldが関数の中にあると、その関数はジェネレーターになる
一度for文やnext()で全部読み終わったジェネレーターは、もう使えないので注意しましょう。
10. 実践:偶数だけを返すジェネレーター
最後に、1から10までの偶数だけを返すジェネレーターを作ってみましょう。
def even_numbers():
for i in range(1, 11):
if i % 2 == 0:
yield i
for num in even_numbers():
print(num)
2
4
6
8
10
このように、if文とyieldを組み合わせることで、条件をつけて値を返すことができます。
まとめ
今回の記事では、Python学習において中級者へのステップアップとして欠かせない「ジェネレーター(generator)」と「yield」の仕組みについて詳しく解説してきました。プログラミングを始めたばかりの頃は、データを扱う際に「リスト(list)」を多用しがちですが、大規模なシステム開発やAI、データ分析の現場では、いかに効率よくメモリを管理するかが重要になります。そこで活躍するのがジェネレーターです。
ジェネレーターの最大の特徴は、「値を一つずつ生成して返す」という遅延評価の仕組みにあります。通常の関数がreturnで処理を完全に終了させてしまうのに対し、yieldは「現在の状態を保持したまま一時停止する」という魔法のような動きをします。これにより、膨大なデータを扱う際でもパソコンのメモリを圧迫せず、スムーズな動作を実現できるのです。
Pythonのジェネレーターを使いこなすための重要ポイント
ジェネレーターを理解する上で、以下の3つのポイントをしっかり押さえておきましょう。これらは実際の開発現場でも頻繁に意識される内容です。
- メモリ効率の劇的な向上: リストは全要素をメモリ上に展開しますが、ジェネレーターは「次に必要な値」だけをその都度計算します。数百万件のレコードを処理する場合でも、ジェネレーターならメモリ消費を最小限に抑えられます。
- イテレータプロトコルの簡潔な実装: 本来、イテレータを自作するには
__iter__や__next__といった特殊メソッドの定義が必要ですが、ジェネレーターを使えばyieldを書くだけで簡単にイテレータとして機能します。 - 無限シーケンスの扱い: 終わりがないデータ列(例えばリアルタイムで発生し続けるログデータなど)を扱う場合、リストでは表現不可能ですが、ジェネレーターなら無限に続く処理もスマートに記述できます。
応用例:ジェネレーター式とフィルタリング
ジェネレーターは関数として定義するだけでなく、リスト内包表記のように一行で書く「ジェネレーター式」という手法もあります。より短く、Pythonらしい(Pythonicな)コードを書く際に非常に便利です。
# リスト内包表記(メモリを消費する)
squares_list = [n**2 for n in range(10)]
# ジェネレーター式(メモリに優しい)
squares_gen = (n**2 for n in range(10))
print(next(squares_gen))
print(next(squares_gen))
0
1
このように、カッコを[]から()に変えるだけでジェネレーターとして機能します。大量のデータをフィルタリングしたり加工したりする際は、この書き方を活用することで、プログラム全体の実行速度や安定性が向上します。
よくあるエラー:StopIterationへの理解
初心者の方が最初につまずきやすいのが、next()関数を使いすぎて発生するStopIterationエラーです。これはエラーというよりは「データの終端に達しました」という通知のようなものです。実務ではfor文を使うことで、Pythonが内部的にこの例外をキャッチして安全にループを終了させてくれるため、明示的に例外処理を書く必要がないケースがほとんどです。
「一度使い切ったら再利用できない」という特性も忘れてはいけません。もし同じデータをもう一度使いたい場合は、再度ジェネレーターオブジェクトを生成(関数を呼び出し)する必要があります。この「使い捨て」の性質こそが、メモリを常にクリーンに保つ秘訣なのです。
生徒
「先生、まとめありがとうございました!ジェネレーターが『その場しのぎ』…じゃなくて、『必要な分だけその都度作る』という賢い仕組みだということがよくわかりました。」
先生
「ははは、その場しのぎという表現もあながち間違いではありませんね。Pythonのメモリ管理においては、その効率の良さが最大の武器になるんです。実際に動かしてみて、何か気づいたことはありますか?」
生徒
「はい!さっき自分で巨大な数字のリストを作ろうとしたらパソコンが重くなったんですけど、ジェネレーターに変えたら一瞬で処理が始まったので驚きました。yieldって魔法の呪文みたいですね。」
先生
「素晴らしい気づきですね。実は、Pythonの組み込み関数であるrange()やzip()なども、内部的にはジェネレーターと同じようなイテレータの仕組みを使っているんですよ。だから大きな数字を指定してもフリーズしないんです。」
生徒
「そうだったんですね!普段何気なく使っていたものにも、この技術が隠れていたなんて。でも、returnとyieldを同じ関数の中で混ぜて使ってもいいんですか?」
先生
「基本的には可能ですが、役割が違います。yieldは値を産出しながら処理を続行し、returnは値を返して関数を完全に終了させます。最近のPython(3.3以降)では、ジェネレーターの中でreturnを書くと、それがStopIterationの引数として扱われるようになりました。」
生徒
「奥が深いですね…。あともう一つ、ジェネレーターって一度使ったら終わりですよね?もし100万個のデータをもう一度最初から読みたくなったら、どうすればいいんでしょう?」
先生
「その場合は、もう一度ジェネレーター関数を呼び出して新しいインスタンスを作ればOKです。インスタンスごとに状態を持っているから、何度でも新しく生成して読み直すことができますよ。」
生徒
「なるほど!リストみたいにずっとメモリに置いておくのではなく、必要な時に都度『製造ライン』を立ち上げるイメージですね。これで大量のログ解析や、ウェブスクレイピングのコードももっと綺麗に書けそうです!」
先生
「その意気です。ジェネレーターはPythonの上級者への入り口とも言える概念です。コードがスッキリするだけでなく、パフォーマンスも向上するので、これから積極的に自作関数に取り入れてみてくださいね。」
生徒
「はい、頑張ります!先生、ありがとうございました!」