Python의 비동기 프로그래밍은 I/O 바운드 작업에서 성능을 극대화합니다. asyncio로 비동기 코드를 작성하고, FastAPI로 현대적인 웹 API를 만들어봅니다.
Python's async programming maximizes performance for I/O-bound operations. Write async code with asyncio and build modern web APIs with FastAPI.
asyncio 기초: async/await asyncio Basics: async/await
async def로 코루틴을 정의하고, await로 비동기 작업을 기다립니다.
Define coroutines with async def and wait for async operations with await.
# 기본 코루틴
async def hello():
print("Hello")
await asyncio.sleep(1)
print("World")
# 코루틴 실행
asyncio.run(hello())
# 값 반환
async def fetch_data() -> str:
await asyncio.sleep(0.5)
return "데이터 로드 완료"
result = asyncio.run(fetch_data())
print(result)
# Basic coroutine
async def hello():
print("Hello")
await asyncio.sleep(1)
print("World")
# Run coroutine
asyncio.run(hello())
# Return value
async def fetch_data() -> str:
await asyncio.sleep(0.5)
return "Data loaded"
result = asyncio.run(fetch_data())
print(result)
동시 실행: gather와 TaskGroup Concurrent Execution: gather & TaskGroup
async def task(name: str, delay: float):
await asyncio.sleep(delay)
return f"{name} 완료"
# asyncio.gather 사용
async def main_gather():
results = await asyncio.gather(
task("A", 1),
task("B", 2),
task("C", 1.5)
)
print(results) # ['A 완료', 'B 완료', 'C 완료']
# Python 3.11+ TaskGroup (권장)
async def main_taskgroup():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(task("A", 1))
task2 = tg.create_task(task("B", 2))
print(task1.result(), task2.result())
async def task(name: str, delay: float):
await asyncio.sleep(delay)
return f"{name} done"
# Using asyncio.gather
async def main_gather():
results = await asyncio.gather(
task("A", 1),
task("B", 2),
task("C", 1.5)
)
print(results) # ['A done', 'B done', 'C done']
# Python 3.11+ TaskGroup (recommended)
async def main_taskgroup():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(task("A", 1))
task2 = tg.create_task(task("B", 2))
print(task1.result(), task2.result())
aiohttp: 비동기 HTTP 클라이언트 aiohttp: Async HTTP Client
import asyncio
async def fetch(url: str) -> dict:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
# 여러 URL 동시 요청
async def fetch_all(urls: list[str]):
async with aiohttp.ClientSession() as session:
tasks = [session.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
return [await r.json() for r in responses]
import asyncio
async def fetch(url: str) -> dict:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
# Concurrent requests to multiple URLs
async def fetch_all(urls: list[str]):
async with aiohttp.ClientSession() as session:
tasks = [session.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
return [await r.json() for r in responses]
FastAPI: 현대적인 웹 프레임워크 FastAPI: Modern Web Framework
FastAPI는 Python 3.7+에서 동작하는 고성능 웹 프레임워크입니다. 자동 문서화, 타입 힌트 기반 검증, 비동기 지원이 특징입니다.
FastAPI is a high-performance web framework for Python 3.7+. Features include auto-documentation, type hint-based validation, and async support.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
# 요청/응답 모델
class User(BaseModel):
name: str
email: str
age: int | None = None
# GET 엔드포인트
@app.get("/")
async def root():
return {"message": "Hello World"}
# 경로 파라미터
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}
# POST 요청
@app.post("/users")
async def create_user(user: User):
return {"created": user.model_dump()}
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
# Request/Response model
class User(BaseModel):
name: str
email: str
age: int | None = None
# GET endpoint
@app.get("/")
async def root():
return {"message": "Hello World"}
# Path parameters
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}
# POST request
@app.post("/users")
async def create_user(user: User):
return {"created": user.model_dump()}
💡 FastAPI 실행하기 💡 Running FastAPI
$ pip install fastapi uvicorn
# 개발 서버 실행
$ uvicorn main:app --reload
# API 문서 확인
# http://localhost:8000/docs (Swagger UI)
# http://localhost:8000/redoc (ReDoc)
pytest: 테스트 작성 pytest: Writing Tests
import pytest
from httpx import AsyncClient
from main import app
# 비동기 테스트
@pytest.mark.asyncio
async def test_root():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
# 파라미터화된 테스트
@pytest.mark.parametrize("user_id,expected", [
(1, {"user_id": 1}),
(42, {"user_id": 42}),
])
@pytest.mark.asyncio
async def test_get_user(user_id, expected):
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get(f"/users/{user_id}")
assert response.json() == expected
import pytest
from httpx import AsyncClient
from main import app
# Async test
@pytest.mark.asyncio
async def test_root():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
# Parameterized test
@pytest.mark.parametrize("user_id,expected", [
(1, {"user_id": 1}),
(42, {"user_id": 42}),
])
@pytest.mark.asyncio
async def test_get_user(user_id, expected):
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get(f"/users/{user_id}")
assert response.json() == expected
✨ 비동기 프로그래밍 Best Practices ✨ Async Programming Best Practices
- await는 코루틴 앞에만 사용
- 동시 실행에는 gather 또는 TaskGroup 사용
- CPU 바운드 작업은 run_in_executor 사용
- 세션/연결은 async with로 관리
- Use await only before coroutines
- Use gather or TaskGroup for concurrency
- Use run_in_executor for CPU-bound tasks
- Manage sessions/connections with async with
⚠️ 흔한 실수들 ⚠️ Common Mistakes
await없이 코루틴 호출 (실행되지 않음)- 동기 함수 안에서
await사용 불가 time.sleep()대신asyncio.sleep()사용
- Calling coroutine without
await(won't execute) - Can't use
awaitinside sync functions - Use
asyncio.sleep()instead oftime.sleep()
