Pythonで for
文を使っていると、「ジェネレータ」という言葉が出てきました。
最初は関数のように見えるのに、next()
が使えたり、for文でも使えたりして混乱しました。
この記事では、学習の記録として「ジェネレータとは何か?」について丁寧にまとめてみます。
1. ジェネレータとは?
ジェネレータとは、yield
を使って値を1つずつ返す関数のようなものです。
普通の関数と違って return
の代わりに、yield
を使って値を一時停止しながら返します。
(実際にはジェネレータ関数内でも return
を使うことはありますが、それは値を返すためではなく、終了を明示するためです)
2. ジェネレータ関数の書き方
def count_up(n):
for i in range(n):
yield i
この関数を呼び出しても、すぐに for
が実行されるわけではありません。
代わりに、「ジェネレータオブジェクト」という特別なイテレータが返ってきます。
gen = count_up(3)
print(gen) # <generator object count_up at 0x...>
3. 値を1つずつ取り出すには?
ジェネレータはイテレータなので、next()
を使って値を1つずつ取り出せます。
gen = count_up(3)
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
# print(next(gen)) # StopIteration 例外になる
もちろん for
文でも扱えます。
for num in count_up(3):
print(num)
4. 普通の関数との違い
比較項目 | 通常の関数 | ジェネレータ関数 |
---|---|---|
終了方法 | return |
yield (一時停止) |
値の返し方 | 1回だけ | 使い切るまで順に返す |
処理の状態 | 呼び出すたびに最初から | 前回の yield から再開 |
イテレータ? | ❌ | ✅ |
5. なぜ使うの?
✅ メモリを節約できる
リストのように全部の値をあらかじめメモリに持つ必要がないため、巨大なデータでも安全に処理できます。
def infinite():
n = 0
while True:
yield n
n += 1
for i in infinite():
if i > 5:
break
print(i)
このように「無限ループのように値を作り続ける」こともできます。
✅ 処理の途中経過をその都度返したいときに便利
たとえばファイルの各行を読み込みながら1行ずつ処理したい場合などに使えます。
6. ジェネレータの注意点
- 一度使い切ると再利用できない(
for
文1回で終了) -
list()
にするとすべての値を取り出してしまう(使い切りに注意) - 途中で
StopIteration
が発生したら終了
g = count_up(3)
print(list(g)) # [0, 1, 2]
print(list(g)) # [](使い切ったあとなので空)
7. 応用:手動で next() を呼び出す
g = count_up(2)
print(next(g)) # 0
print(next(g)) # 1
# print(next(g)) # StopIteration
for
文は内部で自動的に next()
を呼んでくれている、ということがわかりました。
8. まとめ
✔ ジェネレータは yield
を使って、値を1つずつ返す特別な関数
✔ 関数を呼び出すと「ジェネレータオブジェクト(イテレータ)」が返る
✔ メモリ効率がよく、for
文や next()
で繰り返し処理できる
✔ 使い切るまで順に値を返せるが、一度使い切ると再利用はできない
最初は「関数なのにイテレータ?」「listと何が違うの?」と混乱しましたが、
「yield = 一時停止しながら値を返す」というイメージを持つと理解が深まりました。
今後は、リストではメモリがきついような場合や、逐次処理をしたいときに使ってみたいです。