파이썬 비동기 프로그래밍: 비동기 컴프리헨션 완벽 가이드

비동기 컴프리헨션의 핵심 개념

파이썬의 리스트·딕셔너리 컴프리헨션은 간결하고 효율적인 데이터 생성 패턴으로 널리 알려져 있습니다. 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 forawait를 조합할 수 있습니다. 예를 들어 비동기 이터러블에서 나오는 각 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]스트리밍무한 시퀀스, 메모리 효율

비동기 컴프리헨션은 코드 가독성을 높이는 강력한 도구이지만, 실행 특성을 정확히 이해하고 상황에 맞게 선택해야 합니다.

태그: asyncio python-async-await asynchronous-iteration list-comprehension coroutine

7월 25일 23:20에 게시됨