what does async for do? or, how do async iterators in general work, because I’m pretty sure async for just helps you iterate on an async iterator.
I think async iterators have a method __anext__() which “steps through the iterable”? I don’t understand what stepping through means.
Also __anext__() returns an awaitable? what does that mean, especially if I have something like an AsyncIterator[Object], what does the awaitable have to do with the object?
Luckily the Python community is home to giants. sqlalchemy author is one such giant and he has something of interest to contribute to this conversation. But first i bore you with stuff we all already know.
from typing import AsyncIterator, Iterator import contextlib @contextlib.asynccontextmanager async def somecoroutine() -> AsyncIterator[int]: teardownfcn = lambda : None something = 4 try: yield something finally: teardownfcn() @contextlib.contextmanager def somefunction() -> Iterator[int]: teardownfcn = lambda : None something = 4 try: yield something finally: teardownfcn() async with somecoroutine() as stuff: print(f"some int {stuff!s}" with somefunction() as stuff: print(f"some int {stuff!s}"Although not a class, this pops out an AsyncIterator and shows sync equivalent.
From deep in the bowels of sqlalchemy
from sqlalchemy.ext.asyncio.base import GeneratorStartableContext, asyncstartablecontext @asyncstartablecontext async def somecoroutine() -> GeneratorStartableContext[int]: teardownfcn = lambda : print("Cookie monster says, yum yum yum") someiterable = [1,2,3] try: yield from someiterable except GeneratorExit: pass else: teardownfcn() # no teardown gen = await somecoroutine() for thecount in gen: print(f"The Count says, {thecount!s}") # teardown occurs async with gen in somecoroutine(): for thecount in gen: print(f"The Count says, {thecount!s}")Should print
The Count says, 1 The Count says, 2 The Count says, 3 The Count says, 1 The Count says, 2 The Count says, 3 Cookie monster says, yum yum yumThe decorated function can be called either as
async with fn(), orawait fn(). This is decidedly different from what@contextlib.asynccontextmanagersupports, and the usage pattern is different as well.Above,
GeneratorExitis caught if the function were used as anawait. In this case, it’s essential that the cleanup does not occur, so there should not be afinallyblock.If
GeneratorExitis not invoked, this means we’re in__aexit__and we were invoked as a context manager, and cleanup should proceed.So instead of a class with
__anext__and__aiter__an asyncstartablecontext withyield fromcould be a possible alternative.oh wow thanks!

