Python etc / async `for` and `with`

async for and with

Both for and with can be asynchronous. async with uses __aenter__ and __aexit__ magic methods, async for uses __aiter__ and __anext__. All of them are async and you can await within them:

import asyncio

class Sleep:
    def __init__(self, t):
        self._t = t

    async def __aenter__(self):
        await asyncio.sleep(self._t / 2)

    async def __aexit__(self, *args):
        await asyncio.sleep(self._t / 2)

async def main():
    async with Sleep(2):
        print('*')

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

When you implement __iter__ you often don't write an iterator with __next__ method, you just use yield that makes __iter__ a generator:

class Bracketed:                  
    def __init__(self, data):     
        self._data = data         
                                  
    def __iter__(self):           
        for x in self._data:      
            yield '({})'.format(x)
                                  
print(list(Bracketed([1, 2, 3]))) 
# ['(1)', '(2)', '(3)']

PEP 525 allows you to do the same with __aiter__. Both yield and await in the function body make it asynchronous generator. While await is used to communicate with the loop, yield deals with for:

import asyncio                          
                                        
class Slow:                             
    def __init__(self, data, t=1):      
        self._data = data               
        self._t = t                     
                                        
    async def __aiter__(self):          
        for x in self._data:            
            await asyncio.sleep(self._t)
            yield x                     
                                        
async def main():                       
    async for x in Slow([1, 2, 3]):     
        print(x)                        
                                        
loop = asyncio.get_event_loop()         
loop.run_until_complete(main())