web-dev-qa-db-ja.com

通常の関数で非同期関数を呼び出す

私はpython 3.6の非同期にかなり慣れていません

つまり、クラスがあり、そこにいくつかのプロパティを初期化したいということです。そして、プロパティの1つは、非同期関数からの戻り値です。

これを行うためのベストプラクティスは何ですか?

  1. Init関数でevent_loopを1回呼び出して、戻り値を取得しますか?

  2. __init__関数を非同期にしますか?イベントループで実行しますか?

乾杯!

以下は私のコードです:

import asyncio
import aioredis
from datetime import datetime

class C:
    def __init__(self):
        self.a = 1
        self.b = 2
        self.r = None
        asyncio.get_event_loop().run_until_complete(self._async_init())

    async def _async_init(self):
        # this is the property I want, which returns from an async function
        self.r = await aioredis.create_redis('redis://localhost:6379')

    async def heart_beat(self):
        while True:
            await self.r.publish('test_channel', datetime.now().__str__())
            await asyncio.sleep(10)

    def run(self):
        asyncio.get_event_loop().run_until_complete(self.heart_beat())

c=C()
c.run()
7
qichao_he

Init関数でevent_loopを1回呼び出して、戻り値を取得しますか?

___init___中にイベントループをスピンすると、イベントループの実行中にCをインスタンス化できなくなります。 asyncioイベントループ ネストしないでください

[EDIT:質問の2回目の更新後、イベントループは非静的メソッド_C.run_によって実行されるようです。 _run_until_complete_の___init___は、記述されたコードで機能します。ただし、その設計には制限があります。たとえば、イベントループ中にCまたはCのようなクラスのanotherインスタンスを構築することはできません。が走っています。]

___init___関数を非同期にしますか?イベントループで実行しますか?

___init___は、非常に醜いハックに頼らずに非同期にすることはできません。 Pythonの___init___は副作用によって動作し、Noneを返す必要がありますが、_async def_関数はコルーチンオブジェクトを返します。

これを機能させるには、いくつかのオプションがあります。

非同期Cファクトリ

次のようなCインスタンスを返す非同期関数を作成します。

_async def make_c():
    c = C()
    await c._async_init()
    return c
_

このような関数は問題なく非同期であり、必要に応じて待機できます。関数よりも静的メソッドを好む場合、またはクラスで定義されていない関数からプライベートメソッドにアクセスすることに不快感を感じる場合は、make_c()C.create()に置き換えることができます。

非同期_C.r_フィールド

rをその中に格納するだけで、Futureプロパティを非同期にすることができます。

_class C:
    def __init__(self):
        self.a = 1
        self.b = 2
        loop = asyncio.get_event_loop()
        # note: no `await` here: `r` holds an asyncio Task which will
        # be awaited (and its value accessed when ready) as `await c.r`
        self.r = loop.create_task(aioredis.create_redis('redis://localhost:6379'))
_

これには、すべての_c.r_の使用を_await c.r_と綴る必要があります。それが許容できる(または有益でさえある)かどうかは、プログラムの他の場所で使用される場所と頻度によって異なります。

非同期Cコンストラクター

___init___を非同期にすることはできませんが、この制限はその低レベルのいとこ___new___には適用されません。 _T.__new___は、Tのインスタンスでさえないオブジェクトを含む、任意のオブジェクトを返す可能性があります。これは、コルーチンオブジェクトを返すために使用できる事実です。

_class C:
    async def __new__(cls):
        self = super().__new__(cls)
        self.a = 1
        self.b = 2
        self.r = await aioredis.create_redis('redis://localhost:6379')
        return self

# usage from another coroutine
async def main():
    c = await C()

# usage from outside the event loop
c = loop.run_until_complete(C())
_

この最後のアプローチは、それを使用する本当に正当な理由がない限り、本番コードにはお勧めしません。

  • Cインスタンスを返すことを気にしない_C.__new___コンストラクターを定義するため、これはコンストラクターメカニズムの悪用です。
  • Pythonは上記に気づき、(同期)実装を定義または継承した場合でも、_C.__init___の呼び出しを拒否します。
  • await C()の使用は、非同期に慣れている人にとってさえ(または特に)非常に非慣用的に見えます。
4
user4815162342