Learning
레슨 10 / 11·4개 토픽

비동기 프로그래밍과 타입 힌팅

async/await 기초

asyncio는 Python의 비동기 프로그래밍 라이브러리입니다. async def로 코루틴을 정의하고, await로 비동기 작업의 완료를 기다립니다. I/O 바운드 작업(네트워크 요청, 파일 읽기 등)에서 동시성을 확보할 수 있습니다.

python
import asyncio

# 기본 코루틴
async def greet(name, delay):
    await asyncio.sleep(delay)  # 비동기 대기
    print(f"안녕하세요, {name}!")
    return f"{name} 인사 완료"

# 단일 코루틴 실행
asyncio.run(greet("Alice", 1))

# asyncio.gather()로 동시 실행
async def main():
    # 3개 작업을 동시에 시작 (총 2초, 순차라면 6초)
    results = await asyncio.gather(
        greet("Alice", 2),
        greet("Bob", 1),
        greet("Charlie", 1.5),
    )
    print(f"결과: {results}")

asyncio.run(main())

비동기 실전 패턴

python
import asyncio

# 비동기 HTTP 요청 시뮬레이션
async def fetch_url(url: str) -> dict:
    print(f"요청 시작: {url}")
    await asyncio.sleep(1)  # 네트워크 지연 시뮬레이션
    return {"url": url, "status": 200}

async def fetch_all():
    urls = [
        "https://api.example.com/users",
        "https://api.example.com/posts",
        "https://api.example.com/comments",
    ]
    # 모든 요청을 동시에 실행
    tasks = [fetch_url(url) for url in urls]
    results = await asyncio.gather(*tasks)
    for r in results:
        print(f"  {r['url']} → {r['status']}")

# 타임아웃 처리
async def with_timeout():
    try:
        result = await asyncio.wait_for(
            fetch_url("https://slow-api.com"),
            timeout=0.5
        )
    except asyncio.TimeoutError:
        print("타임아웃 발생!")

# 비동기 이터레이터
async def countdown(n):
    for i in range(n, 0, -1):
        yield i
        await asyncio.sleep(0.5)

async def main():
    async for num in countdown(5):
        print(num)

타입 힌팅 기초

타입 힌트는 코드의 가독성을 높이고 IDE 자동 완성과 정적 분석 도구(mypy)를 활용할 수 있게 합니다. Python 3.9+에서는 내장 타입을 직접 사용할 수 있고, 3.10+에서는 X | Y 유니온 구문을 지원합니다.

python
# 기본 타입 힌트
def greet(name: str) -> str:
    return f"Hello, {name}!"

# 컬렉션 타입 (Python 3.9+)
def average(numbers: list[float]) -> float:
    return sum(numbers) / len(numbers)

def word_count(text: str) -> dict[str, int]:
    counts: dict[str, int] = {}
    for word in text.split():
        counts[word] = counts.get(word, 0) + 1
    return counts

# Optional과 Union (Python 3.10+)
def find_user(user_id: int) -> dict | None:
    users = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
    return users.get(user_id)

def parse_input(value: str | int) -> str:
    return str(value)

# 이전 버전 호환 (3.9 이전)
from typing import Optional, Union, List, Dict
def legacy_find(user_id: int) -> Optional[Dict[str, str]]:
    pass

typing 모듈 심화

python
from typing import TypeVar, Generic, Protocol, Callable

# TypeVar — 제네릭 함수
T = TypeVar("T")

def first(items: list[T]) -> T:
    return items[0]

print(first([1, 2, 3]))       # int 추론
print(first(["a", "b"]))      # str 추론

# Generic 클래스
class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()

int_stack: Stack[int] = Stack()
int_stack.push(42)

# Protocol — 구조적 타이핑 (덕 타이핑)
class Drawable(Protocol):
    def draw(self) -> str: ...

class Circle:
    def draw(self) -> str:
        return "○"

class Square:
    def draw(self) -> str:
        return "□"

def render(shape: Drawable) -> None:
    print(shape.draw())  # Circle, Square 모두 가능

# Callable 타입
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

print(apply(lambda x, y: x + y, 3, 4))  # 7
  • async def — 코루틴(비동기 함수)을 정의하는 키워드
  • await — 비동기 작업의 완료를 기다림 (async 함수 안에서만 사용)
  • asyncio.run() — 이벤트 루프를 시작하고 코루틴을 실행
  • asyncio.gather() — 여러 코루틴을 동시에 실행하고 결과를 모음
  • TypeVar — 제네릭 함수나 클래스에서 타입 매개변수 정의
  • Generic[T] — 타입 매개변수를 가진 제네릭 클래스 생성
  • Protocol — 구조적 타이핑(덕 타이핑)을 위한 인터페이스 정의
💡

asyncio는 CPU 바운드 작업에는 적합하지 않습니다. CPU 집약적 작업은 multiprocessing이나 concurrent.futures.ProcessPoolExecutor를 사용하세요. 타입 힌트는 런타임에 강제되지 않으므로, mypy로 정적 분석을 실행해야 실질적인 타입 검사가 이루어집니다.