curious course on coroutines and concurrency
pythoncurious course on coroutines and concurrencyのメモ。
これは、asyncioがなかった時代に、yield構文を使って、非同期プログラミングを解説した講座。Pyconの講演だったらしい。
yieldとcoroutine
普通、yieldはiteratorを定義するために使われる。こんな感じ:
def rep(n):
x = 0
while x < n:
yield x
x += 1
for i in rep(3):
print(i)
0
1
2
このジェネレーターには、実は、「外から値を渡す」ことができる。こんな感じ:
def receive():
while True:
x = yield
print(x)
r = receive()
next(r)
for i in range(3):
r.send(i)
0
1
2
値の送受信を両方とも書くこともできる。こんな感じ:
def rands():
r = yield "S"
print(r)
rs = rands()
s = rs.send(None)
print(s)
rs.send("R")
S
R
Traceback (most recent call last):
File "/Users/hotoku/junk/2022/03/24-080501.py", line 9, in <module>
rs.send("R")
StopIteration
このプログラムは、次のように動作する。
rs = rands()でジェネレーターrsが作られる(ここでは、randsの中のコードは実行されない)s = rs.send(None)で、次のようなことが起こるrandsがスタートし、yield "S"まで進む- この
yieldされた値"S"が、nextの返り値となる - グローバルの変数
sに上の返り値が代入される
print(s)で"S"が表示されるrs.send("R")で、次のようなことが起こるsendの引数"R"が、yield "S"の返り値となり、randsのロケール変数rに代入されるprint(r)で"R"が表示される- ジェネレーターの最後に到達したので、
StopIterationが送出される
- 誰も
StopIterationを受け取っていないので、プログラムが例外終了する
つまり、
<generator>.send(None)で、グローバル→ジェネレーターに制御が移り、ジェネレーターの処理が開始されるyield <value>で、ジェネレーター→グローバルに制御が戻る<generator>.send(v)で、グローバル→ジェネレーターに制御が移る
ということが繰り返されることが分かる。
また、ジェネレーターから、グローバルに処理の終了を知らせるにはStopIterationが使われていることが分かる。
逆に、グローバルからジェネレーターに終了を知らせるには、<generator>.close()を呼び出す。すると、待機しているyieldの行でGeneratorExit例外が発生する。こんな感じ:
def loop():
try:
while True:
v = yield
print(v)
except GeneratorExit:
print("finished")
l = loop()
l.send(None)
l.send(1)
l.send(2)
l.send(3)
l.close()
1
2
3
finished