비동기 컴프리헨션의 핵심 개념
파이썬의 리스트·딕셔너리 컴프리헨션은 간결하고 효율적인 데이터 생성 패턴으로 널리 알려져 있습니다. asyncio 환경에서는 이 개념이 확장되어 비동기 컴프리헨션(async comprehension)을 제공합니다. 이를 통해 비동기 이터러블이나 코루틴 컬렉션을 우아하게 처리할 수 있습니다.
기본 컴프리헨션 복습
기존 동기 환경의 컴프리헨션을 먼저 살펴봅시다. 단일 표현식으로 시퀀스를 생성하는 파이썬 특유의 문법입니다.
# 리스트 컴프리헨션
squares = [x ** 2 for x in range(10)]
# 딕셔너리 컴프리헨션
mapping = {k: v for k, v in zip(['x', 'y', 'z'], [10, 20, 30])}
# 세트 컴프리헨션
unique_vals = {n % 5 for n in range(100)}
async for 기반 비동기 컴프리헨션
비동기 이터러블(비동기 제너레이터나 __aiter__를 구현한 객체)을 순회할 때 async for 키워드를 컴프리헨션 내부에 삽입합니다. 이 방식은 내부적으로 각 이터레이션 단계에서 __anext__ 호출을 await 처리합니다.
import asyncio
async def fetch_pages():
async for page in web_crawler(): # 비동기 이터러블 순회
yield await download(page)
# 비동기 리스트 컴프리헨션
cached = [html async for html in fetch_pages()]
중요한 제약사항: async for 표현식은 반드시 async def로 정의된 코루틴 내부나 asyncio.create_task로 생성된 태스크 컨텍스트에서만 유효합니다.
await 기반 컴프리헨션
이미 확보된 awaitable 객체들의 컬렉션을 처리할 때는 await 키워드를 컴프리헨션 내부에 배치합니다. 각 요소를 순차적으로 resolve하여 결과를 모읍니다.
async def gather_results(pending_ops):
# pending_ops: 코루틴 객체 또는 Future 객체의 시퀀스
resolved = [await op for op in pending_ops]
return resolved
주의할 점은 이 방식이 순차 실행임을 인지해야 합니다. 병렬 처리가 필요하다면 asyncio.gather(*pending_ops)나 asyncio.as_completed를 활용하세요.
혼합 패턴: 중첩 비동기 컴프리헨션
더 복잡한 시나리오에서는 async for와 await를 조합할 수 있습니다. 예를 들어 비동기 이터러블에서 나오는 각 awaitable을 즉시 resolve하는 경우:
async def process_batch(sources):
# sources: 비동기 이터러블, 각 항목은 awaitable
outcomes = [await item async for item in sources]
return [o for o in outcomes if o.success]
실전 예제: 데이터 파이프라인
실제 워크플로우에서의 활용을 보여드립니다:
import asyncio
import aiohttp
async def fetch_status(session, endpoint):
async with session.get(endpoint) as response:
return endpoint, response.status
async def health_check_monitor(urls):
async with aiohttp.ClientSession() as sess:
# 코루틴 객체 생성
checks = (fetch_status(sess, u) for u in urls)
# await 컴프리헨션으로 순차 실행
# 또는 asyncio.gather(*checks)로 병렬 처리
results = [await chk for chk in checks]
failures = [url for url, code in results if code != 200]
return failures
# 실행
asyncio.run(health_check_monitor([
'https://api.example.com/health',
'https://db.example.com/ping'
]))
성능 고려사항
| 패턴 | 실행 방식 | 적합한 상황 |
|---|---|---|
[await x for x in items] | 순차 | IO 순서 보장, 속도 제한 필요 |
asyncio.gather(*items) | 동시 | 최대 처리량, 순서 무관 |
[x async for x in stream] | 스트리밍 | 무한 시퀀스, 메모리 효율 |
비동기 컴프리헨션은 코드 가독성을 높이는 강력한 도구이지만, 실행 특성을 정확히 이해하고 상황에 맞게 선택해야 합니다.