我怎样才能在未来的对象中等待__await__?

时间:2015-10-29 09:06:07

标签: python python-3.x async-await python-asyncio

PEP 0492添加了新的__await__魔术方法。实现此方法的对象变为类似future的对象,可以使用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')

我该如何解决?

6 个答案:

答案 0 :(得分:30)

使用直接__await__()电话:

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

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

解决方案由Yury Selivanov(PEP 492的作者)推荐aioodbc library

答案 1 :(得分:13)

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

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

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

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

这是有效的,因为__await__方法返回一个迭代器(参见PEP 492),所以返回另一个__await__的迭代器就可以了。

这当然意味着我们无法改变原先等待的暂停行为。更一般的方法是镜像await关键字并使用yield from - 这可以让我们将多个等待的迭代器组合成一个:

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

额外位 - 这两者有什么区别?

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

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

第一个变量是一个普通函数:当你调用它时,执行do_...并返回一个迭代器。第二个是发电机功能;调用它根本不执行任何代码!只有当第一次返回返回的迭代器时才会执行do_...。这在以下方面有所不同,有点人为的情况:

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

答案 2 :(得分:5)

我不明白为什么我不能__await__内的原生协程中获得收益,但看起来可以从生成器协程中生成{ 生成器协程中的{1}}和来自本地协程。它有效:

__await__

答案 3 :(得分: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'

答案 4 :(得分:2)

使用装饰器。

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

答案 5 :(得分:0)

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