生成器和协同程序的异步装饰器

时间:2019-02-15 16:03:27

标签: python python-3.x asynchronous python-asyncio python-decorators

该问题与synchronous version中的同一问题有关。 目标是设计装饰器,该装饰器将生成器或协程作为输入。我的代码如下:

import asyncio


def say_hello(function):

    async def decorated(*args, **kwargs):
        return_value = function(*args, **kwargs)
        if isinstance(return_value, types.AsyncGeneratorType):
            print("Hello async generator!")
            async for v in return_value:
                yield v
        else:
            print("Hello coroutine!")
            return await return_value

    return decorated


@helpers.say_hello
async def generator():
    for i in range(5):
        await asyncio.sleep(0.2)
        yield i

@helpers.say_hello
async def coroutine():
    await asyncio.sleep(1)
    return list(range(5))


async def test():
    for v in generator():
        print(v)
    for v in coroutine():
        print(v)

给出的错误是:

'return' with value in async generator

我猜测这是根据decorated包含yieldreturn以及值的事实进行静态检查的。

有什么办法可以使这项工作吗? (除了在say_hello中使用参数来指定function是生成器还是协程)。

1 个答案:

答案 0 :(得分:1)

您基本上可以使用其他答案所部署的相同机制,但适用于协程。例如:

def say_hello(function):
    def decorated(*args, **kwargs):
        function_instance = function(*args, **kwargs)
        if isinstance(function_instance, types.AsyncGeneratorType):
            async def inner():
                print("Hello async generator!")
                async for v in function_instance:
                    yield v
        else:
            async def inner():
                print("Hello coroutine!")
                return await function_instance
        return inner()
    return decorated

请注意,在这种情况下,decorated是使用def而不是async def定义的。这样可以确保协程对象上的异步生成器-迭代器在调用时立即开始运行并能够选择要返回的内容。由于decorated 返回一个通过调用用async def inner定义的函数创建的对象,因此它在功能上等效于async def本身。

您的test函数不正确,因为它使用for遍历异步生成器,并遍历协程。相反,它应该是async def,并使用async for遍历异步生成器,并使用await等待协程。我使用以下代码进行测试(generatorcoroutine不变):

async def test():
    async for v in generator():
        print(v)
    await coroutine()

asyncio.run(test())
# or, on Python 3.6 and older:
#asyncio.get_event_loop().run_until_complete(test())