본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] async for와 async with의 2가지 핵심 내부 매커니즘 차이와 구현 방법

by Papa Martino V 2026. 3. 17.
728x90

async for와 async with
async for와 async with

 

파이썬의 비동기 프로그래밍이 async/await 문법으로 정착되면서, 우리는 단순히 코루틴을 호출하는 것을 넘어 자원을 관리하고 데이터를 스트리밍하는 세련된 방법을 가지게 되었습니다. 그 중심에는 비동기 이터레이터(Async Iterator)비동기 컨텍스트 매니저(Async Context Manager)가 있습니다. 하지만 async forasync with가 런타임에서 어떤 특수 메서드(Magic Methods)를 호출하며, 동기 방식과 메모리 레이아웃 측면에서 어떤 차이가 있는지 이해하는 개발자는 드뭅니다. 오늘 이 글에서는 그 내부 구현의 심연을 들여다봅니다.


1. 내부 프로토콜: 동기 vs 비동기 매커니즘 차이

파이썬 인터프리터는 for문이나 with문을 만날 때 특정 프로토콜을 준수하는 객체인지 확인합니다. 비동기 문법은 여기에 '대기(awaitable)'의 개념이 추가된 형태입니다.

구분 비동기 루프 (async for) 비동기 컨텍스트 (async with) 구현상의 결정적 차이
필수 매직 메서드 1 __aiter__ __aenter__ 반환값이 코루틴인지 여부
필수 매직 메서드 2 __anext__ __aexit__ 반복 지속 vs 자원 해제
내부 동작 원리 StopAsyncIteration 발생 시 종료 진입/퇴장 시점에 await 수행 이벤트 루프의 태스크 일시 중단
주요 목적 비동기 데이터 스트림 소비 비동기 연결/파일 자원 관리 네트워크 I/O 효율성 극대화

2. async for의 내부 구현: 비동기 데이터 스트리밍 해결

async for는 데이터를 한꺼번에 메모리에 올리지 않고, 필요할 때마다 비동기적으로 가져오는 제너레이터(Generator)의 비동기 버전입니다. 내부적으로 인터프리터는 다음과 같은 순서로 실행을 시도합니다.

  • it = obj.__aiter__()를 호출하여 비동기 이터레이터를 가져옵니다.
  • 루프의 매 단계에서 val = await it.__anext__()를 호출합니다.
  • 데이터가 준비될 때까지 이벤트 루프에 제어권을 양보하며 대기합니다.
  • StopAsyncIteration 예외가 발생하면 루프를 안전하게 종료합니다.

3. async with의 내부 구현: 안전한 자원 관리 방법

네트워크 소켓이나 DB 커넥션은 열고 닫는 과정 자체가 비동기 작업일 때가 많습니다. 이때 async with는 자원의 획득과 해제를 보장하는 최적의 해결 방법입니다.

해결책: 비동기 락(Lock)과 세마포어 활용

임계 구역(Critical Section)을 보호할 때 async with lock: 형식을 사용하면, 다른 코루틴이 락을 점유하고 있을 때 현재 코루틴은 차단(Blocking)되지 않고 비차단(Non-blocking) 상태로 대기 큐에 들어갑니다.

4. 실전 샘플 예제 (Sample Example)

이론을 실제 코드로 구현해 보겠습니다. 비동기적으로 숫자를 생성하는 클래스와 자원을 관리하는 클래스를 직접 작성해 보는 것이 이해에 가장 빠릅니다.


import asyncio

# 1. 커스텀 비동기 이터레이터 구현 (async for용)
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:
            raise StopAsyncIteration
        await asyncio.sleep(0.5)  # 비동기 작업 시뮬레이션
        self.count += 1
        return self.count

# 2. 커스텀 비동기 컨텍스트 매니저 구현 (async with용)
class AsyncResource:
    async def __aenter__(self):
        print("자원을 연결하는 중 (awaitable)...")
        await asyncio.sleep(1)
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("자원을 안전하게 해제하는 중 (awaitable)...")
        await asyncio.sleep(0.5)

async def main():
    # async with 사용 예시
    async with AsyncResource() as res:
        print("자원 사용 중...")
        # async for 사용 예시
        async for num in AsyncCounter(3):
            print(f"데이터 수신: {num}")

if __name__ == "__main__":
    asyncio.run(main())

5. 성능 최적화와 주의사항

내부 구현을 알면 실수도 줄어듭니다. __aiter__ 메서드 자체는 일반 함수여야 하며 async def로 선언하지 않는 것이 표준입니다. 반면 __anext__, __aenter__, __aexit__는 반드시 코루틴 객체를 반환하는 async def로 선언되어야 런타임 에러를 해결할 수 있습니다.


내용 출처 및 참고 문헌

  • Python PEP 492 – Coroutines with async and await syntax.
  • Python Official Docs: "Asynchronous Iterators & Context Managers".
  • Real Python: "Asynchronous Iterators in Python".
  • Fluent Python (2026 Edition) - Control Flow and Metaprogramming.
728x90