The PEP 492 mentions that:
async with EXPR as VAR:
BLOCK
is semantically equivalent to:
mgr = (EXPR)
aexit = type(mgr).__aexit__
aenter = type(mgr).__aenter__
VAR = await aenter(mgr)
try:
BLOCK
except:
if not await aexit(mgr, *sys.exc_info()):
raise
else:
await aexit(mgr, None, None, None)
However, VAR = await aenter(mgr)
is not in the try
block so I am wondering if __aenter__()
is allowed to fail.
For example, in this this aiohttp
snippet (taken from Getting Started):
import aiohttp
import asyncio
async def main():
async with aiohttp.ClientSession() as session:
async with session.get('http://python.org') as response:
print("Status:", response.status)
print("Content-type:", response.headers['content-type'])
html = await response.text()
print("Body:", html[:15], "...")
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
session.get('http://python.org')
could fail and __aexit__()
would not be called to close the context.
If __aenter__
fails, __aexit__
is indeed not run. Any required cleanup is __aenter__
's responsibility in this case.
__aenter__
has more information about how far it got and what did or did not get successfully initialized, so having __aenter__
handle this is more convenient than expecting __aexit__
to handle cleaning up arbitrary partially-entered context manager states.
(This is exactly the same for normal, non-async context managers.)