我如何在类未来对象的 __await__ 中等待?

How can I await inside future-like object's __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 函数,我不能使用 yield from 因为原生协程需要 await 表达式:

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')

我该如何解决?

我不明白为什么我不能从 __await__ 中的原生协程 中 yield,但看起来可以从生成器中 yield __await__ 内的协程 生成器协程 内的原生协程 产生。有效:

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()))

使用直接__await__()调用:

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

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

该解决方案由 Yury Selivanov(PEP 492) for aioodbc library

的作者推荐

要在 __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'

简短版本:await foo 可以替换为 yield from foo.__await__()


结合其他答案的所有想法 -

在最简单的情况下,只需委托给另一个等待的作品:

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

这是有效的,因为 __await__ 方法 return 是一个迭代器(参见 PEP 492),所以 return 使用另一个 __await__ 的迭代器是可以的.

当然,这意味着我们根本无法改变原始等待的挂起行为。更通用的方法是镜像 await 关键字并使用 yield from - 这让我们可以将多个 awaitables 的迭代器合并为一个:

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

这里有一个要点:这与第一个变体不完全一样! yield from 是一个表达式,所以要和以前完全一样,我们还需要 return 该值:

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

这直接反映了我们如何使用 await 语法编写正确的委托:

    return await new_sleep()

extra bit - 这两者有什么区别?

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

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

第一个变体是一个普通函数:当你调用它时,do_... 被执行并且一个迭代器 returned。第二个是生成器函数;调用它根本不会执行我们的任何代码!只有当 returned 迭代器第一次被 yield 时才会执行 do_...。这在以下有点人为的情况下有所不同:

def foo():
    tmp = Waiting.__await__()
    do_something()
    yield from tmp

使用装饰器。

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()

您的原始代码在 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 - 简单的生成器迭代器委托。