简体   繁体   English

使用aiohttp嵌套“与...同步”

[英]nested “async with” using aiohttp

I would like to create a scheduler class that uses aiohttp to make API calls. 我想创建一个使用aiohttp进行API调用的调度程序类。 I tried this: 我尝试了这个:

import asyncio
import aiohttp

class MySession:
    def __init__(self):
        self.session = None

    async def __aenter__(self):
        async with aiohttp.ClientSession() as session:
            self.session = session
            return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.session:
            await self.session.close()

async def method1():
    async with MySession() as s:
        async with s.session.get("https://www.google.com") as resp:
            if resp.status == 200:
                print("successful call!")

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

but this just results in an error: RuntimeError: Session is closed . 但这只会导致错误: RuntimeError: Session is closed A second approach for the __aenter__ function: __aenter__函数的第二种方法:

    async def __aenter__(self):
        self.session = aiohttp.ClientSession()
        return self

works well. 效果很好。 Is this a good construct? 这是一个好结构吗? It doesn't adhere to examples of how to use aiohttp. 它不遵循如何使用aiohttp的示例。 Also wondering why the first approach isn't working? 还想知道为什么第一种方法不起作用?

You can't use with inside a function and have the context manager remain open, no. 您不能使用with一个函数里,并有上下文管理器保持打开状态,没有。 The with with aiohttp.ClientSession() as session: block exits as soon as you use return to exit the __aenter__ coroutine! 当您使用return退出__aenter__协程时with with aiohttp.ClientSession() as session:块将立即退出!

For the specific case, entering a aiohttp.ClientSession() context manager does nothing but return self . 对于特定情况,进入aiohttp.ClientSession()上下文管理器aiohttp.ClientSession() 返回self So for that type , just creating the instance and storing it in self.session , and awaiting on self.session.close() suffices here, yes. 因此,对于该类型 ,只需创建实例并将其存储在self.session ,然后等待self.session.close()就可以了,是的。

The general pattern for a nested asynchronous context manager is to await the __aenter__ and __aexit__ methods of a nested async context manager from your own such methods (and perhaps pass along the exception information): 嵌套异步上下文管理器的一般模式是从您自己的此类方法等待嵌套异步上下文管理器的__aenter____aexit__方法(并可能传递异常信息):

class MySession:
    def __init__(self):
        self.session = None

    async def __aenter__(self):
        self.session = aiohttp.ClientSession()
        await self.session.__aenter__()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.session:
            return await self.session.__aexit__(exc_type, exc_val, exc_tb)

Technically speaking, you should first assure that there is an actual __aexit__ attribute before entering a nested context manager: 从技术上讲,在进入嵌套上下文管理器之前,您应该首先确保有一个实际的__aexit__属性:

class MySession:
    def __init__(self):
        self.session = None
        self._session_aexit = None

    async def __aenter__(self):
        self.session = aiohttp.ClientSession()
        self._session_aexit = type(self.session).__aexit__
        await self.session.__aenter__()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.session:
            return await self._session_aexit.__aexit__(
                self.session, exc_type, exc_val, exc_tb)

See the official PEP that added the concept . 请参阅添加了该概念官方PEP

You can manage that dependency externally: 您可以从外部管理该依赖关系:

import asyncio
import aiohttp

class MySession:

    def __init__(self, client_session):
        self.session = client_session

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        pass

async def method1():
    async with aiohttp.ClientSession() as client_session:
        async with MySession(client_session) as s:
            async with s.session.get("https://www.google.com") as resp:
                if resp.status == 200:
                    print("successful call!")

asyncio.run(method1())

When that async with chain becomes too ridiculous you can use AsyncExitStack : async with链的async with变得太荒谬时,可以使用AsyncExitStack

from contextlib import AsyncExitStack

async def method1():
    async with AsyncExitStack() as stack:
        client_session = await stack.enter_async_context(aiohttp.ClientSession())
        s = await stack.enter_async_context(MySession(client_session))
        async with s.session.get("https://www.google.com") as resp:
            if resp.status == 200:
                print("successful call!")

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM