web-dev-qa-db-ja.com

未来のようなオブジェクトの__await__の内部でどのように待つことができますか?

PEP 0492 は新しい__await__マジックメソッドを追加します。このメソッドを実装するオブジェクトは未来のオブジェクトになり、awaitを使用して待機できます。それは明確だ:

import asyncio


class Waiting:
    def __await__(self):
        yield from asyncio.sleep(2)
        print('ok')

async def main():
    await Waiting()

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

では、async defではなくasyncio.sleepで定義された関数を呼び出したい場合はどうでしょうか。 __await__await関数ではないため、asyncを使用できません。ネイティブコルーチンにはawait式が必要なため、yield fromを使用できません。

async def new_sleep():
    await asyncio.sleep(2)

class Waiting:
    def __await__(self):
        yield from new_sleep()  # this is TypeError
        await new_sleep()  # this is SyntaxError
        print('ok')

どうすれば解決できますか?

29

直接__await__()呼び出しを使用:

async def new_sleep():
    await asyncio.sleep(2)

class Waiting:
    def __await__(self):
        return new_sleep().__await__()

ソリューションは、Yury Selivanov( PEP 492 の作成者)が aioodbcライブラリ に対して推奨しました

30
Andrew Svetlov

ショートバージョン:_await foo_はyield from foo.__await__()で置き換えることができます


他の答えからのすべてのアイデアを組み合わせる-

最も単純なケースでは、他の待機可能な作品に委任するだけです:

_def __await__(self):
    return new_sleep().__await__()
_

これは、___await___メソッドがイテレーターを返すため( PEP 492 を参照)、別の___await___のイテレーターを返すので問題ありません。

もちろん、これは、待機可能なオリジナルのサスペンション動作をまったく変更できないことを意味します。より一般的なアプローチは、awaitキーワードをミラーリングし、_yield from_を使用することです。これにより、複数のawaitableのイテレータを1つに結合できます。

_def __await__(self):
    # theoretically possible, but not useful for my example:
    #yield from something_else_first().__await__()
    yield from new_sleep().__await__()
_

これが問題です:これは最初のバリアントとまったく同じことをしていません! _yield from_は式なので、以前とまったく同じようにするには、その値も返す必要があります。

_def __await__(self):
    return (yield from new_sleep().__await__())
_

これは、await構文を使用して適切な委任を作成する方法を直接反映しています。

_    return await new_sleep()
_

余分なビット-これらの2つの違いは何ですか?

_def __await__(self):
    do_something_synchronously()
    return new_sleep().__await__()

def __await__(self):
    do_something_synchronously()
    return (yield from new_sleep().__await__())
_

最初のバリアントは単純な関数です。これを呼び出すと、_do_..._が実行され、イテレータが返されます。 2つ目はジェネレーター関数です。それを呼び出しても、コードはまったく実行されません!返されたイテレータが初めて生成されるときのみ、_do_..._が実行されます。これにより、次のような少し不自然な状況に違いが生じます。

_def foo():
    tmp = Waiting.__await__()
    do_something()
    yield from tmp
_
13
Silly Freak

__await__内でネイティブコルーチンからの生成ができない理由を理解できませんでしたが、__await__内でジェネレータコルーチンからの生成およびyield fromネイティブコルーチンその内部ジェネレータコルーチン。できます:

async def new_sleep():
    await asyncio.sleep(2)

class Waiting:
    def __await__(self):
        @asyncio.coroutine
        def wrapper(coro):
            return (yield from coro)
        return (yield from wrapper(new_sleep()))
5

__await__関数内で待機するには、次のコードを使用します。

async def new_sleep():
    await asyncio.sleep(1)


class Waiting:
    def __await__(self):
        yield from new_sleep().__await__()
        print('first sleep')
        yield from new_sleep().__await__()
        print('second sleep')
        return 'done'
5
crvv

デコレータを使用します。

def chain__await__(f):
    return lambda *args, **kwargs: f(*args, **kwargs).__await__()

次に__await__ネイティブコルーチンとして。

async def new_sleep():
    await asyncio.sleep(2)

class Waiting:
    @chain__await__
    async def __await__(self):
        return await new_sleep()
4
Huazuo Gao

Mikhailのバージョンを次のように簡略化することもできます。

async def new_sleep():
    await asyncio.sleep(2)

class Waiting:
    def __await__(self):
        async def wrapper():
            await new_sleep()
            print("OK")
        return wrapper()
0
Qeek

元のコードはPython 3.6で問題なく動作しました。

クイックフィックス(12文字)

他の回答の修正は印象的であり、それらの一部が機能することは信じられませんでした(しかし、機能します!)。しかし、asyncioモデルが変更され続けると、これらの修正は機能しなくなります。

私は3.7で動作する最小限の修正をラッパーなしで持っています:

_import asyncio


class Waiting:
    def __await__(self):
        yield from asyncio.sleep(2).__await__()
        print('ok')


async def main():
    await Waiting()


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
_

元のコードとの唯一の違いは、ラッパーがなくても.__await__()asyncio.sleep(2)に追加することです。

互換性アプローチ

_sync_await_でawaitにしたいジェネレーターを囲むこの___await___ラッパーを使用することもできます。

_import asyncio


def sync_await(gen):
    if hasattr(gen, '__await__'):
        # 3.7, and user defined coroutines in 3.6
        print('yield from gen.__await__()')
        return (yield from gen.__await__())
    else:
        # 3.6, only native coroutines like asyncio.sleep()
        print('yield from gen')
        return (yield from gen)


class Waiting:
    def __await__(self):
        yield from sync_await(asyncio.sleep(2))
        print('ok')


async def main():
    await Waiting()


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
_

注-ラッパーはreturn (yield from ...)を実行しませんが、_yield from_-単純なジェネレーターイテレーター委譲です。

0
Tomasz Gandor