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