kb84tkhrのブログ

何を書こうか考え中です あ、あと組織とは関係ないってやつです 個人的なやつ

ジェネレータのお勉強

どうもジェネレータがちゃんとわかった感ないので調べる

たいていgoogleで検索してQiitaとかで済ませるんだけど
今回は公式ドキュメントから見てみよう

3.7.3rc1 Documentation

お、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__()を使えばyieldNoneを返し、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の説明もよくわかってない