In Python Lan Ref. 3.4.4, it is said that __aenter__() and __aexit__() must return awaitables. However, in the example async context manager, these two methods return None:
class AsyncContextManager:
async def __aenter__(self):
await log('entering context')
async def __aexit__(self, exc_type, exc, tb):
await log('exiting context')
Is this code correct?
__aenter__ and __aexit__ must return awaitables, but look what happens when you call the ones in the example:
>>> class AsyncContextManager:
... async def __aenter__(self):
... await log('entering context')
... async def __aexit__(self, exc_type, exc, tb):
... await log('exiting context')
...
>>> AsyncContextManager().__aenter__()
<coroutine object AsyncContextManager.__aenter__ at 0x7f5b092d5ac0>
It didn't return None! We got a coroutine object, which is awaitable.
These methods are async functions, which automatically return (awaitable) asynchronous coroutines. return statements in the body of an async function determine what gets returned when you await the coroutine, not what gets returned when you call the function.
This is similar to how generator functions return generator iterators, even though they usually have no return statement, and how if you write __iter__ as a generator function, you should not try to return an iterator inside the generator function.
So what happens if you do put a return in __aenter__ or __aexit__ defined as async functions? Well, you can, and if you do, the return statement doesn't have to return an awaitable. Here's what Python will do.
If you return something from an __aenter__ defined as an async function, that determines what gets bound to an as target, if the async with uses as.
If you return something from an __aexit__ defined as an async function, that determines whether to suppress exceptions that got raised inside the block. A "truthy" value tells the async with to suppress exceptions, while a "falsy" value tells the async with to let exceptions propagate. The default None is falsy, so by default, exceptions are not suppressed.
Here's an example:
import asyncio
class Example:
async def __aenter__(self):
return 3
async def __aexit__(self, exc_type, exc, tb):
return True
async def example():
async with Example() as three:
print(three == 3)
raise Exception
print("Exception got suppressed")
asyncio.run(example())
Output:
True
Exception got suppressed
Your __aenter__ method must return a context.
class MyAsyncContextManager:
async def __aenter__(self):
await log('entering context')
# maybe some setup (e.g. await self.setup())
return self
async def __aexit__(self, exc_type, exc, tb):
# maybe closing context (e.g. await self.close())
await log('exiting context')
async def do_something(self):
await log('doing something')
usage:
async with MyAsyncContextManager() as context:
await context.do_something()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With