はじめに
こんにちは。T.H.です。
今回は、Pythonの中でも使いどころが限られる、ジェネレータについてです。
ジェネレータとは
Pythonのジェネレータとは、yield式で値を返却する関数のことを指します。
ジェネレータから返されたオブジェクトをジェネレータイテレータといいます。このネーミングのせいか、両者はよく混同されています。混乱を避けるためにここではジェネレータをジェネレータ関数、ジェネレータ関数とジェネレータイテレータをまとめてジェネレータと呼ぶことにします。
おそらくもっとも身近なジェネレータ関数はrange()
でしょう。ちなみに私はジェネレータを知る前はrange()
はfor文に使えるがリストではない、時々わざわざListに変換しないと動かない、面倒なものが返ってくるというイメージをもっていました……。
ジェネレータ関数の書き方
通常の関数のように記述し、returnの代わりにyieldを入れるように記述します。
ジェネレータイテレータは名前の通りイテラブルなため、for文に組み込むことが出来ます。
# 0 < limitまでカウントアップするジェネレータ
def countup_generator(limit=5):
counter = 0
while True:
if counter >= limit:
break
yield counter
counter += 1
# 呼び出し側
for num in countup_generator(5):
print(num)
ジェネレータイテレータがfor等で呼ばれるとnext経由でジェネレータ関数が呼ばれます。ジェネレータ関数はyieldで処理を停止し、counterの値を返却します。もう一度呼ばれると停止した次点から再開します。つまりcounter += 1
が実行され、whileループが続行されます。その時の状態はすべて保存されたままとなります。
実行結果
0
1
2
3
4
ジェネレータの使いどころ
シンプルな例ではList等との差が分かりにくいところですが、集合の全体を保持せず順次処理していくため、Listで確保するには巨大すぎるデータの処理や、再帰出実装した場合に深くなりすぎる場合などに有効になります。
もう少し細かく定義しますと、下記になるかと思います。
- 前回(+α)の状態を保持する必要がある
- 全体を順次処理するが、全体を知る必要がない
条件にはまればかなり強力な機能だとは思いますが、有効に使える条件が厳しく、思ったより自力で実装する機会は少ない、何とも言えない立ち位置にいる機能だと感じています。ただ、range()
のように下回りの処理では使われているため、機能自体の理解は必要かとは考えています。
注意点
自力実装する場合
状態を持つため、ライフタイムの勘違いによるバグを埋め込みやすいため注意しましょう。
呼び出し側の場合
Listなどと似て非なる挙動をするため、混同しないようにしましょう。処理途中でlenが必要になってもジェネレータでは原理上(Listなどに変換しない限り)取れません。
最後に
少し短いですが以上になります。今回は単なるPythonの入門記事のようになってしまったため、次回はもう少し変わった内容を記載できるとよいかなと思いました。