自分で非同期libraryを書いているのですが、実行中のコルーチンが自身のコルーチンオブジェクトを得る簡単な方法はありますか?例えば以下のような感じです。
python
1async def async_fn(): 2 自身のcoroutineを得る何らかの関数() 3 4async def main(): 5 await my_async_library.複数のcoroutineの完了を待機(async_fn(), async_fn(), ...他にも色々なcoroutine) 6 7my_async_library.run(main())
asyncio
においてはcurrent_task()
という関数を使って実行中のcoroutineを得られるようなのでちょっと実装codeを覗いてみたのですが、どうやらglobal変数を用いて実行中のtaskを自分で管理しているようです。そんな事をせずとも例えばinspect.get_current_coroutine()
のような形で簡単に実行中のcoroutineを得る方法があれば教えてほしいです。
最終的にやりたい事
最終的にやりたいのは とあるGUI libraryのイベントのcallback関数からcoroutine.throw()
を呼ぶことで、問題はcallback関数がそのcoroutineのinner関数として作られることです。だから実行中のcoroutineから自身のcoroutine objectを得て、それをcallback関数から見えるようにしたいです。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答2件
0
余談になりますが、フレームオブジェクトを利用しての実装。
コルーチンオブジェクトの捕捉はデコレータですが、
受け渡し方についての別アプローチです。
第一引数にコルーチンオブジェクトを直接渡す
python
1""" 2Update f_local via frame object 3see more details https://www.python.org/dev/peps/pep-0558/ 4 5> The PyFrame_LocalsToFast() function will be changed to always emit RuntimeError, 6explaining that it is no longer a supported operation, 7and affected code should be updated to use PyFrame_GetLocals(frame), 8PyFrame_GetLocalsCopy(frame), or PyFrame_GetLocalsView(frame) instead. 9 10""" 11 12import asyncio 13import inspect 14import functools 15import ctypes 16 17 18def self_coro_instance(func): 19 assert inspect.iscoroutinefunction(func) 20 @functools.wraps(func) 21 def _func(*args, **kw): 22 # 第一引数は仮で適当な値を置きます 23 coro = func(None, *args, **kw) 24 assert inspect.iscoroutine(coro) 25 26 # 第一引数の変数名は "coro" として、 27 # フレームオブジェクト経由でコルーチン内のローカル変数 coro を置き換え 28 frame = coro.cr_frame 29 frame.f_locals.update({"coro": coro}) 30 31 # 通常は、f_locals は、書き換えても反映されませんが、 32 # 変更を反映する内部API を呼び出し。 33 # (※ 但し、このAPIは廃止が検討されているようです) 34 ctypes.pythonapi.PyFrame_LocalsToFast( 35 ctypes.py_object(frame), ctypes.c_int(1)) 36 37 return coro 38 return _func 39 40 41async def async_fn1(): 42 coro = async_fn2() 43 print(coro) 44 await coro 45 46 47@self_coro_instance 48async def async_fn2(coro): 49 print(coro) 50 await asyncio.sleep(1) 51 52 53async def main(): 54 await asyncio.gather(async_fn1()) 55 56 57if __name__ == '__main__': 58 asyncio.run(main()) 59
欠点: 処理系依存してしまう上に、先の方法と比べても、恩恵は関数呼び出しの括弧が外れるだけなので、
このコード自体は、お勧めの方法という訳ではありません。
get_current_coroutine() の実装。
コルーチンオブジェクトをコルーチン内のローカル変数に保持します。
import asyncio import inspect import functools import ctypes def self_coro_instance(func): assert inspect.iscoroutinefunction(func) @functools.wraps(func) def _func(*args, **kw): coro = func(*args, **kw) assert inspect.iscoroutine(coro) frame = coro.cr_frame frame.f_locals.update({"__coro__": coro}) ctypes.pythonapi.PyFrame_LocalsToFast( ctypes.py_object(frame), ctypes.c_int(1)) return coro return _func def get_current_coroutine(): # 注意: デコレーターを多重に使う状況は想定してません。 frame = inspect.currentframe().f_back return frame.f_locals.get("__coro__", None) async def async_fn1(): coro = async_fn2() print(coro) await coro @self_coro_instance async def async_fn2(): # ローカル変数一覧を表示 print(locals()) # ローカル変数 __coro__ は存在しますが、 # 実行時にデコレータでの代入の為、コンパイル時には存在しません。 # print(__coro__) はバイトコードでは、グローバル変数参照となる為 NameError # locals() 経由で参照 print(locals()["__coro__"]) coro = get_current_coroutine() print(coro) await asyncio.sleep(1) async def main(): await asyncio.gather(async_fn1()) if __name__ == '__main__': asyncio.run(main())
実用に当たっては、PyFrame_LocalsToFast API の動向
(代替方法や、各バージョンでの状況)を調べる必要がありそうなので、
上記のコード自体は、ライブラリ向きではないデモンストレーションなのですが、
ここで紹介したかったのは、フレームオブジェクトの存在で、
inspect モジュール内部で良く扱われる実行時環境の情報です。
コルーチンの場合 coro.cr_frame.f_locals でコルーチン内のローカル変数にアクセス可能です。
これを応用して、例えば、inner関数で callback をという事でしたが、
コルーチン内のローカル変数を利用する為に inner 関数 にする必要があったのだとしたのなら、
別の方法としては、外部から callback を登録する方法にして、
必要なコルーチン内のローカル変数を coro.cr_frame.f_locals 経由で参照する、
等も考えられるのではないでしょうか。※ 但し処理系に依存する可能性は高い。
(実際の状況は把握してないので、この辺りは想像です)
投稿2020/09/05 20:49
編集2020/09/05 20:51総合スコア8760
0
ベストアンサー
「現在実行中のcoroutineを得る方法」
素直にデコレーターで実装してみました。
第一引数に、「実行中のコルーチンを得る関数」を渡します。
python
1 2import asyncio 3import inspect 4import functools 5 6def Y(func): 7 assert inspect.iscoroutinefunction(func) 8 @functools.wraps(func) 9 def _func(*args, **kw): 10 coro = None 11 coro = func((lambda: coro), *args, **kw) 12 assert inspect.iscoroutine(coro) 13 return coro 14 return _func 15 16async def async_fn1(): 17 coro = async_fn2() 18 print(coro) 19 await coro 20 21@Y 22async def async_fn2(getCoro): 23 coro = getCoro() 24 print(coro) 25 await asyncio.sleep(1) 26 27async def main(): 28 await asyncio.gather(async_fn1()) 29 30 31if __name__ == '__main__': 32 asyncio.run(main()) 33
デメリット: デコレータを使うと型チェックやannotation 周りがすっきりしない。
定義と実際の呼び出しに齟齬が出てしまいます。
inspect 等の実行時情報を参照するアプローチ
そんな事をせずとも例えばinspect.get_current_coroutine()のような形で
簡単に実行中のcoroutineを得る方法があれば教えてほしいです。
inspect関連の多くは処理系依存になる為、以下はCPythonを前提とします。
inspect.currentframe で参照できるスタックフレームの情報ですが、
この実行時情報にコルーチンの情報は含まれません。
正確には、C言語側での構造体 にはあるかもしれないが、
Pythonからアクセス可能な情報 PyFrameObjectとしてはないようです。
より具体的には、f_gen がコルーチン内部のジェネレータ・オブジェクト(※ ここは確認してないので自信なし)を持っているようですが frame_memberlist には f_gen が無い。
一応、状況によっては使える事もあります、
- ローカル変数にはアクセスできるので、変数名を予め規約として決めておけば、
変数名決め打ちでアクセスする等は可能です。しかし、汎用的な解決策には出来ません。
- ctypes を使い、アドレス範囲を特定して探す方法も考えられますが、簡単な方法ではありません。
CPython処理系の知識が要求され、デバッグが困難になります。
内部仕様の変更にも弱いので、お勧めできる方法ではありません。
個人的な意見ですが、
外部へ提供されてない内部情報は、理由があって隠蔽しているはずなので、
「実行中のコルーチンを得る」には独自に管理する方が無難だと思います。
最終的にやりたいのは とあるGUI libraryのイベントのcallback関数からcoroutine.throw()を呼ぶことで、
別の方法を検討した方が脈はありそうです。
想定している状況に合うかは解らないのですが、
coroutine.throw() は外部からコルーチン内で例外を発生させる方法なので、
内部からなら通常の例外(raise ~)が使えるはずです。
投稿2020/09/05 06:23
編集2020/09/05 18:28総合スコア8760
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/09/07 04:45 編集
2020/09/06 05:23
2020/09/06 08:35 編集
2020/09/06 09:36
2020/09/09 00:38
2020/09/11 11:22
2020/09/12 20:14