python curio を試してみたよ。
基本的な考え方としては「async/await を使う」。つまり「タスクを作って回す」になる。タスクは async
キーワードを付けた関数の形で宣言する。interactive shell では「タスクを回す」ものを用意して実行することになる。昔から asyncio
があったけれども、curio
でもよい。あるいは coroutine method を呼び出してもよい。
>>> async def hello():
... return "Hello"
...
>>> import curio
>>> curio.run(hello())
'Hello'
>>> import asyncio
>>> asyncio.get_event_loop().run_until_complete(hello())
'Hello'
>>> try:
... hello().send(None)
... except StopIteration as e:
... e.value
...
'Hello'
curio
だと短くていいね。
こうなると asyncio
と curio
の比較になってくる。asyncio
は async/await 構文ができる前からあることもあって、async/await に最適な設計というわけではないようだ。curio
に慣れていくと、asyncio
はガードレールのない高速道路のような感覚がしてくる。uvloop
で速くなるよ と言われても、躊躇する。
asyncio 作者。https://www.youtube.com/watch?v=m28fiN9y_r8
curio 作者。Live coding が鮮やか https://www.youtube.com/watch?v=ZzfHjytDceU https://www.youtube.com/watch?v=MCs5OvhV9S4
一方で、async/await それ自体の癖も相当ある。標準ライブラリは暗黙的に socket
オブジェクトを中で使うけれども、普通に使うと I/O がブロッキングモードになって、組み合わせるときに苦労する。標準ライブラリとは別に、socket
を取り除いてあって、プロトコル部分に絞ってステートマシン的に使えるライブラリもある。例えば h11
や hyper h2
は async/await の中では比較的組み合わせやすい。
h2 作者。https://www.youtube.com/watch?v=7cC3_jGwl_U
この方向でいくつか試していくと、monkey_patch
しない gevent
, eventlet
のような感じになってくる。curio
との比較で言うと、monkey_patch で生じる激甚な副作用と便利さを秤にかける感じになるだろうか。
最後の最後まで問題になるのは database 接続。SQLAlchemy 使うユースケースはまだ模索中…。構造を持った大きなファイルを seek して回るパターンも良いプロトコルパーサを作るのは難しい。
ちなみに開発者ドキュメントに Please, Don’t Use Curio! と書いてあって、笑ってしまった。そうですねぇ……geventがPEP492な __await__
を実装してくれれば、鬼に金棒なのだけど。asyncio で事故らない自信が持てないんだよなぁ。
TIPS
async/await 使う場合は await 漏れを検出できるのはありがたい。
if __name__=="__main__":
import logging
logging.getLogger("asyncio").setLevel(logging.DEBUG)
import gc
gc.set_debug(gc.DEBUG_UNCOLLECTABLE)
loop = asyncio.get_event_loop()
loop.set_debug(True)
try:
loop.run_forever()
finally:
loop.close()