개요
Python 비동기 프로그래밍을 공부하다가 async with와 함께 async for이라는 구문을 발견했다. async with은 비동기 콘텍스트 관리자인데 async for는 어떤 역할인지 공부하지 않아 이번 기회에 공부해보려고 한다.
비동기 반복
async for은 비동기 이터레이터를 탐색하는 데 사용한다. 여기서 비동기 이터레이터란 __aiter__, __anext__ 메서드를 구현한 객체에 해당한다.
- __aiter__ : self, 객체 자신을 반환해야 한다.
- __anext__ : 이터레이터의 다음 항목을 반환하는 코루틴 메서드여야 한다.
즉, async for은 StopAsyncIteration이 발생할 때까지 비동기 이터레이터의 __anext__ 메서드가 반환하는 객체를 탐색한다.
async for a in async_iterable:
await do_a_thing(a)
문법은 위와 같은데, 이를 풀어쓰면 아래와 같다.
it = async_iterable.__aiter__()
while True:
try:
a = await anext(it)
except StopAsyncIteration:
break
await do_a_thing(a)
비동기 반복은 반복하는 대상이 비동기적으로 데이터를 제공할 때 사용한다. 비동기 스트림, 웹소켓 데이터 수신, 비동기 파일 읽기 등의 대표적인 예시가 된다.
for VS async for
async for는 비동기 방식이라는 점을 제외하면 for문과 다른 부분이 없으며, 시간이 걸리는 작업을 하나씩 기다리면서 반복하는 구조라고 생각하면 된다.
구분 | for | async for |
반복 대상 | 이터러블 객체 (리스트, 튜플, 제너레이터 등) | 비동기 이터러블 객체 |
사용 예 | for x in [1, 2, 3]: | async for x in some_async_iterable: |
동작 방식 | 한 번에 값을 꺼내옴 | 값을 기다려서(async) 꺼내옴 |
구현
비동기 이터레이터를 구현하기 위해서는 __aiter__, __anext__ 메서드를 구현해야 한다.
아래 예시는 1초 마다 숫자를 생성하는 비동기 이터레이터를 구현한 것이다. 반복을 끝낼 때는 StopAsyncIteration을 발생시키면 된다.
import asyncio
class AsyncCounter:
def __init__(self, limit):
self.limit = limit
self.count = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.count < self.limit:
await asyncio.sleep(1) # 비동기적으로 1초 대기
self.count += 1
return self.count
else:
raise StopAsyncIteration
구현한 클래스는 다음과 같이 사용하면 된다.
async def main():
async for number in AsyncCounter(3):
print(datetime.datetime.now(), number)
asyncio.run(main())
의도한 대로 1초에 숫자 하나씩 생성하는 모습을 확인할 수 있다.
참고 문서
https://velog.io/@atesi/Python-Asyncio06