Pythonでコーディングしていると、簡単な状態機械の実装など、たまに関数にコルーチン的な動作をしてほしくなるときがある。ただasync/awaitとかイベント駆動の仕組みをいちいち書きたくない場合も多い。
そんな時に、ジェネレータのyield
文を使って値をやり取りすることで、イベント駆動の仕組みを作らなくても逐次的に関数(というかジェネレータ)と値をやり取りすることができるようになる。
ポイント
- ジェネレータとして問題のルーチンを定義する
- 内部的には
yield
文で値を返し、その返り値として値を受け取る。
- 内部的には
- ジェネレータに対して
iter()
でイテレータを生成する。- イテレータの
send()
メソッドで値を渡しつつ、その返り値としてジェネレータからの値を受け取る。 -
send()
メソッドを使い始める前に、一度だけnext()
を呼んでジェネレータを「起動」しないといけない。
- イテレータの
サンプル
>>> def shifter():
... # 一度next()を呼び出すことで、以下のルーチンが始まる。
...
... # 呼び出し側からの最初のsend()メソッドで、状態の値を初期化
... state = yield
... while state is not None:
... # 現在のstateの値を呼び出し側に返す。
... # 呼び出し側はsend()で次のstateの値を入力できる
... state = yield state
... # send()でNoneを入力するかnext()を用いることで
... # yield文の返り値はNoneになる
...
... # 終了時の処理
... yield "done"
...
>>> gshift = iter(shifter()) # ジェネレータからイテレータを生成。この状態ではまだ「起動」されていない
>>> next(gshift) # これで初めてジェネレータが起動する
# (ジェネレータ側の処理は、関数定義の5行目の`state = yield`のところで停止した状態)
# 次の呼び出しでsend()を用いることで5行目の`state = yield`に処理が戻る
# whileループの中の`state = yield state`のところで、現在のstateの値とともに処理が呼び出し側に返る
>>> gshift.send(0)
0 # 入力した`0`が返ってきた
>>> gshift.send(1)
1
>>> gshift.send('Hello')
'Hello'
>>> gshift.send(None) # next(gshift) でも同じことができる
'done' # 最後のyieldの値が返ってきた
# この段階で、ジェネレータの処理はすでに終了している
# この後にnext()やsend()を呼んでも、StopIterationが送出されるだけ