在 asyncio 中混合异步上下文管理器和直接等待

Mixinig async context manager and straight await in asyncio

如何搭配

async with api.open() as o:
    ...

o = await api.open()

在一个函数中?

因为首先需要 __aenter____aexit__ 的对象,但第二次需要 __await__,它应该是没有 await 的生成器。

我的尝试是:

def AsyncContext(aenter, aexit):

    class AsyncContextClass:

        async def __aenter__(self):

            return await aenter()

        async def __aexit__(self, *args):

            return await aexit(*args)

        def __await__(self):

            return (yield from aenter())

    return AsyncContextClass()

但是如果 aenter 定义为 async def (TypeError: cannot 'yield from' a coroutine object in a non-coroutine generator).

则失败 __await__

它与 aenter@asyncio.coroutine 装饰器配合使用效果很好,但这很“脏”。

您可以从 class 的 __await__ return __aenter____await__:

# vim: tabstop=4 expandtab

import asyncio

class Test(object):

    async def __aenter__(self):
        print("enter")

    async def __aexit__(self, *args):
        print("exit")

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

async def run():
    async with Test():
        print("hello")

    await Test()

loop = asyncio.get_event_loop()
loop.run_until_complete(run())
loop.close()

输出:

enter
hello
exit
enter

在 python 3.8.5 中你可以这样做

from httpx import AsyncClient
from asyncio import run


async def main():
    api_base_url = 'https://google.com/'

    api_session_build = AsyncClient(base_url=api_base_url)

    # Enter Async Context manager

    apisession = await api_session_build.__aenter__()

    # Do stuff

    print( await apisession.get('/photos/about/'))

    # Exit Async Context Manager

    await apisession.__aexit__()

run(main())