ジェネレータのお勉強
どうもジェネレータがちゃんとわかった感ないので調べる
たいていgoogleで検索してQiitaとかで済ませるんだけど
今回は公式ドキュメントから見てみよう
お、3.7.3rc1になってる
けどそれはおいといてgeneratorを検索
意外とそれっぽいページが出てこないなあ
Glossaryでgeneratorの項を探す
あった
- generatorはgenerator iteratorを返す関数
- yieldで一連の値を生成して、for文やnext()で使う
generator iteratorはそのすぐ下に説明
- generator関数で生成されたオブジェクト
うん循環参照ですね
- yieldは呼ばれるたびに実行中の場所を覚えて実行を中断し、実行を再開ときは覚えておいた場所から再開する
それはだいたい知ってた
これだけではわかった気がしないのでyieldのリンク先を見る
- 関数定義の中でyieldを使えばgenerator functionになる
ここで新しい情報はそれくらいかな
なかなかそれっぽい説明が出てこないね
こんどはYield expressionsへ
お、ここはけっこうボリュームある
async defってやつは今日は無視
- generator functionが呼ばれると、generatorというiteratorが返される
ちょっとまてややこしい
generatorはgenerator iteratorを返す関数で
generator functionが呼ばれると、generatorというiteratorが返される
ここは気にしないほうがいいところかもしれない
かすかな例
def gen():
yield 123
最小のジェネレータはこれでいいってことだな
どれどれ
$ python3
>>> def gen():
... yield 123
...
>>> gen
<function gen at 0x10908ef28>
>>> gen()
<generator object gen at 0x1092c22b0>
>>> for n in gen():
... print(n)
...
123
gen
がgenerator functionで
gen()
がgeneratorまたはgenerator iteratorってことでいいのかな
- generatorはgenerator functionの実行をコントロールする
- 実行はgeneratorのメソッドが呼ばれた時点ではじまり、最初のyieldまで進み、generatorの呼び出し元に値を返して実行を中断する
- 再度generatorのメソッドを呼ぶと、yieldがあたかも関数呼び出しであったかのように実行が継続する
- 呼び出し側が
__next__()
を使えばyield
はNone
を返し、send()
を使えば渡した値を返す
どれどれ
ちょっとさっきのgen()ではさすがに試しづらいからこんな感じで
>>> def gen():
... a = yield 1
... print("first yield", a)
... a = yield 2
... print("second yield", a)
... a = yield 3
... print("third yield", a)
__next__()
から試す
>>> g = gen()
>>> g.__next__()
1
>>>
まずは「最初のyieldまで進み、generatorの呼び出し元に値を返して実行を中断する」までが実行されたというわけだな
>>> g.__next__()
first yield None
2
お、そこから始まることになるのか
ほんとに値を返した瞬間に止まってるんだな
いまも、2を返したところで止まってて、たぶんa
は1のままなんだろう
続けて
>>> g.__next__()
second yield None
3
>>>
aに2が代入されてprintを実行して3を返したところで止まる
>>> g.__next__()
third yield None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
aに2が代入されてprintを実行して関数が終わるのでStopIteration
StopIterationを返すよっていう説明あったかなあ
まあいいか
ところでアンダーバーふたつで囲った名前だと普通はそのまま呼ばないイメージ・・・
next(g)
でよかったのか
ところでこういう書き方はできない模様
>>> def gen():
... print("first yield", yield 1)
File "<stdin>", line 2
print("first yield", yield 1)
これかなあ
Deprecated since version 3.7: Yield expressions deprecated in the implicitly nested scopes used to implement comprehensions and generator expressions.
いやそれ以前にこれは式じゃないのかも
yield_atom ::= "(" yield_expression ")"
っていう定義があるからカッコでくくって初めて式になるってことかな
>>> def gen2():
... print((yield 1))
...
>>> h = gen2()
>>> next(h)
1
>>> next(h)
None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
そうみたい
いちおう進んでもいいかなくらいにはわかった気がするけど
もうすこし実例もみたいなあ
PEP 255でも見てみるか
あとyield from
の説明もよくわかってない