파이썬185번5 MIN
async / await 문법
async def로 코루틴을 정의하고 await로 결과를 기다리는 문법의 정확한 의미, awaitable의 종류, async for와 async with, 코루틴과 제너레이터의 차이를 정리합니다.
이 글은 PALDYN Tech Blog의 글입니다.원문 보기
지난 글에서 작업을 취소하고 타임아웃을 거는 법을 다뤘다. 지금까지 async와 await를 자연스럽게 써 왔지만, 이 두 키워드가 정확히 무엇을 의미하는지 한 번 짚고 넘어갈 필요가 있다. 문법의 의미를 정확히 알면 흔한 오류를 피할 수 있다.
async def는 코루틴 함수를 만든다
async def로 정의한 함수는 일반 함수가 아니라 코루틴 함수다. 이 함수를 호출하면 본문이 실행되는 대신 코루틴 객체가 반환된다. 실제 실행은 그 코루틴을 await하거나 루프에 등록해야 시작된다.
async def add(a, b):
return a + b
coro = add(1, 2) # 본문은 아직 실행 안 됨
print(type(coro)) # <class 'coroutine'>
import asyncio
print(asyncio.run(add(1, 2))) # 3 — 여기서 실행
await는 결과를 기다리며 양보한다
await는 두 가지를 동시에 한다. 첫째, 대상이 끝날 때까지 현재 코루틴을 멈추고 결과를 받아 온다. 둘째, 멈춰 있는 동안 이벤트 루프에 제어권을 양보해 다른 코루틴이 실행되게 한다. 이 양보가 asyncio 동시성의 핵심이다.
await는 반드시 async def 함수 안에서만 쓸 수 있다. 일반 함수나 모듈 최상단에서 쓰면 SyntaxError가 난다.
await할 수 있는 것 — awaitable
아무것이나 await할 수 있는 것은 아니다. await의 대상은 awaitable이어야 한다. awaitable에는 세 종류가 있다.
import asyncio
async def main():
# 1) 코루틴
await asyncio.sleep(0.1)
# 2) Task / Future
task = asyncio.create_task(asyncio.sleep(0.1))
await task
# 3) await 5 → TypeError: object int can't be used in 'await' expression
asyncio.run(main())
일반 값(정수, 문자열, 리스트 등)은 awaitable이 아니므로 await 대상이 될 수 없다. 동기 함수의 반환값을 await하려다 TypeError를 만나는 것이 초보자의 흔한 실수다.
async for와 async with
async는 반복문과 컨텍스트 매니저에도 붙는다. 비동기 이터레이터를 도는 async for는 각 항목을 가져올 때마다 await할 수 있고, async with는 진입/종료 시점에 비동기 작업(예: 비동기 연결 열고 닫기)을 수행한다.
import asyncio
async def main():
# 비동기 컨텍스트 매니저 (예: DB 연결, HTTP 세션)
async with open_connection() as conn:
# 비동기 이터레이터에서 한 줄씩
async for row in conn.stream("SELECT ..."):
process(row)
이 둘도 async def 안에서만 쓸 수 있다. 비동기 이터레이터와 컨텍스트 매니저의 구현 자체는 별도의 글에서 자세히 다룬다.
코루틴은 제너레이터의 사촌이다
내부적으로 코루틴은 제너레이터와 비슷한 메커니즘 위에 서 있다. 둘 다 실행 도중에 멈췄다가 재개할 수 있다. 차이는 제너레이터가 yield로 값을 바깥에 내보내며 멈추는 반면, 코루틴은 await로 다른 작업의 완료를 기다리며 멈춘다는 점이다.
# 제너레이터: 값을 생산하며 일시정지
def counter():
n = 0
while True:
yield n # 값을 내보내고 멈춤
n += 1
# 코루틴: 다른 작업을 기다리며 일시정지
async def fetch_all(urls):
for url in urls:
await download(url) # 완료를 기다리며 멈춤
이 구조적 유사성 덕분에 둘 다 "중단 가능한 함수"라는 같은 뿌리에서 출발한다. 다음 글에서는 asyncio에서 가장 위험한 함정인, 블로킹 I/O가 루프 전체를 멈추는 문제와 그 해결책을 다룬다.
지난 글: asyncio 작업 취소
다음 글: 블로킹 I/O와 asyncio
읽어주셔서 감사합니다. 😊

