这是示例代码。
class Foo:
def __init__(self):
self._run_coro()
def _run_coro(self):
async def init():
bar = #some I/O op
self.bar = bar
loop = asyncio.get_event_loop()
loop.run_until_complete(init())
async def spam(self):
return await #I/O op
async def main():
foo = Foo()
await foo.spam()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
运行此代码时,出现以下异常:
RuntimeError: This event loop is already running
如果我在Foo
之外初始化main
,则代码将毫无例外地运行。我想初始化Foo
,以便在初始化过程中运行一个协程,该协程创建一个类属性bar
。
我无法确定如何正确执行。如何从__init__
运行协程。
任何帮助将不胜感激。
class Foo:
def __init__(self):
self.session = requests.Session()
self.async_session = None
#I guess this can be done to initialize it.
s = self.init_async_session()
try:
s.send(None)
except StopIteration:
pass
finally:
s.close()
async def init_async_session(self):
#ClientSession should be created inside a coroutine.
self.async_session = aiohttp.ClientSession()
初始化self.async_session
答案 0 :(得分:2)
如果某些方法使用异步方法,则应将其明确定义为异步方法。这是asyncio
背后的核心思想:让您以一种始终知道某种任意方法是否可以执行异步操作的方式编写代码。
在您的代码段中,您想在同步方法bar
中执行异步操作(__init__
I / O),而asyncio
则禁止这样做。您应该使_run_coro
异步并异步初始化Foo
,例如using __await__
方法:
import asyncio
class Foo:
def __await__(self):
return self._run_coro().__await__()
async def _run_coro(self): # real async initializer
async def init():
await asyncio.sleep(1) # bar I/O
self.bar = 123
await init()
return self
async def spam(self):
return await asyncio.sleep(1) # I/O op
async def main():
foo = await Foo()
await foo.spam()
asyncio.run(main()) # instead of two lines in Python 3.7+
您可能有兴趣阅读this answer,以更好地了解asyncio
的工作方式和处理方式。
更新:
s = self.init_async_session() try: s.send(None)
不要这样做:生成器的方法只是协程的实现细节。您可以预测协程对调用.send()
方法的反应,您可以依靠此行为。
如果要执行协程,请使用await
,如果要“在后台”启动协程,请使用task或asyncio
doc中的其他功能。
初始化
的正确方法是什么self.async_session
涉及aiohttp.ClientSession
时,不仅应创建它,而且还应将其正确关闭。最好的方法是使用aiohttp
doc中所示的异步上下文管理器。
如果您想在Foo
中隐藏此操作,则可以使其成为异步管理器。完整示例:
import aiohttp
class Foo:
async def __aenter__(self):
self._session = aiohttp.ClientSession()
await self._session.__aenter__()
return self
async def __aexit__(self, *args):
await self._session.__aexit__(*args)
async def spam(self):
url = 'http://httpbin.org/delay/1'
resp = await self._session.get(url)
text = await resp.text()
print(text)
async def main():
async with Foo() as foo:
await foo.spam()
asyncio.run(main())
更新2:
您可以组合使用多种方法从上方初始化/关闭对象,以获得所需的结果。只要您牢记这两个操作都是异步的,因此应该等待,一切都很好。
另一种可能的方式:
import asyncio
import aiohttp
class Foo:
def __await__(self):
return self._init().__await__()
async def _init(self):
self._session = aiohttp.ClientSession()
await self._session.__aenter__()
return self
async def close(self):
await self._session.__aexit__(None, None, None)
async def spam(self):
url = 'http://httpbin.org/delay/1'
resp = await self._session.get(url)
text = await resp.text()
print(text)
async def main():
foo = await Foo()
try:
await foo.spam()
finally:
await foo.close()
asyncio.run(main())
答案 1 :(得分:0)
这是我的解决方法。
class Session:
def __init__(self, headers):
self._headers = headers
self._session = requests.Session()
self._async_session = None
async def _init(self):
self._session = aiohttp.ClientSession(headers=headers)
async def async_request(self, url):
while True:
try:
async with self._async_session.get(url) as resp:
resp.raise_for_status()
return await resp.text()
except aiohttp.client_exceptions.ClientError:
#retry or raise
except AttributeError:
if isinstance(self._async_session, aiohttp.ClientSession):
raise
await self._init()
def request(self, url):
return self._session.get(url).text
async def close(self):
if isinstance(self._async_session, aiohttp.ClientSession):
await self._session.close()
async def main():
session = Session({})
print(await session.async_request('https://httpstat.us/200')
await session.close()
asyncio.run(main())
我可以初始化Session
类,并进行同步和异步请求。我不必显式调用await session._init()
来初始化self._async_session
,就像在调用session._async_request
且self._async_session
为None时那样,将调用await session._init()
并且请求将重试。