Reputation: 199
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?
Upvotes: 4
Views: 9463
Reputation: 281758
__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())
True
Exception got suppressed
Upvotes: 7
Reputation: 47
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()
Upvotes: 3