Python은 동적 언어이지만, 타입 힌트(type hint)를 붙이면 편집기와 정적 분석기가 더 많은 오류를 미리 알려줍니다. 이번 편에서는 typing 모듈의 대표 기능과 Protocol을 이용한 인터페이스 설계법을 알아봅니다. 어려운 용어를 한 번에 다 이해하려고 하기보다, "함수에 기대 타입 메모 남기기" → "딕셔너리 구조를 적어두기" → "인터페이스를 이름 대신 동작으로 표현하기" 순서로 차근차근 진행합니다.
이번 글에서 새로 나오는 용어
- TypedDict: 딕셔너리 키와 값의 타입을 클래스처럼 정의하는 도구
- 정적 분석기:
mypy,pyright처럼 코드를 실행하지 않고 타입 힌트를 검사해 오류를 찾아주는 프로그램
개념 정리
학습 메모
- 소요 시간: 70~80분(필수 파트 기준)
- 준비물: 함수·클래스 설계, 예외 처리, pytest 기반 코드베이스
- 학습 목표: 핵심 함수에 타입 힌트를 붙이고 Protocol 기반 인터페이스를 만든 뒤 정적 검사 돌리기
- 타입 힌트는 함수와 변수에 기대 타입을 적어 두는 주석입니다.
- Protocol은 덕 타이핑을 정적으로 설명하는 구조적 인터페이스입니다.
- TypedDict는 딕셔너리 키와 값을 문서화하는 타입 도구입니다.
- 정적 분석기는 mypy, pyright처럼 타입 힌트를 읽고 오류를 찾아줍니다.
- Core 섹션을 먼저 해치우고, "선택 확장" 표시가 있는 파트는 나중에 읽어도 충분합니다.
코드로 이해하기
타입 힌트 기본기 (Core)
def grade(score: int) -> str:
if score >= 90:
return "A"
if score >= 80:
return "B"
return "C"
score: int: 매개변수 타입을 표시합니다.-> str: 반환 타입을 명시합니다.
Python 인터프리터는 힌트를 강제하지 않지만, mypy, pyright 같은 도구가 정적 검사를 수행합니다.
CLI 파라미터에 힌트 붙이기 (Core)
Typer 명령 함수를 떠올려 보세요. 문자열을 숫자로 착각하거나 None을 처리하지 않으면 CLI가 곧바로 실패합니다.
from mealbot.notifier import SlackNotifier
def send_meal(school_code: str, *, limit: int | None = None) -> list[str]:
"""급식 정보를 받아 메시지 목록으로 돌려줍니다."""
data = fetch_meal(school_code)
menu: list[str] = data["menu"]
if limit is not None:
menu = menu[:limit]
return menu
def push_to_slack(menu: list[str], notifier: SlackNotifier) -> None:
for line in menu:
notifier.send(line)
- 함수 시그니처만 읽어도 어떤 값이 들어오고 나가는지 알 수 있습니다.
limit: int | None처럼 허용 범위를 명시하면 호출부에서Optional처리 여부를 쉽게 눈치챌 수 있습니다.list[str]같은 구체적인 타입 덕분에 편집기가menu.append(...)를 자동완성합니다.
컬렉션과 옵션 타입 (Core)
from typing import List, Dict, Optional
users: List[str] = ["민지", "준호"]
scores: Dict[str, int] = {"민지": 92}
maybe_score: Optional[int] = scores.get("지훈")
Optional[int]는int또는None을 의미합니다.- Python 3.9 이상에서는
list[str],dict[str, int]처럼 내장형에 직접 대괄호를 사용할 수 있습니다.
타입 별칭과 TypedDict (Optional)
from typing import TypedDict
class User(TypedDict):
name: str
is_active: bool
def deactivate(user: User) -> User:
user["is_active"] = False
return user
TypedDict는 딕셔너리 구조를 정해두고 키/값 타입을 문서화하는 도구입니다.
Protocol 소개 (Core → Plus)
typing.Protocol은 덕 타이핑(duck typing)을 정적으로 설명하는 추상 인터페이스입니다. 특정 메서드 시그니처만 맞으면 클래스 이름과 상관없이 호환되도록 만들 수 있습니다.
from typing import Protocol
class Notifier(Protocol):
def send(self, message: str) -> None:
...
def broadcast(notifier: Notifier, payload: str) -> None:
notifier.send(payload)
class SlackNotifier:
def send(self, message: str) -> None:
print(f"slack: {message}")
class SMSNotifier:
def send(self, message: str) -> None:
print(f"sms: {message}")
SlackNotifier와 SMSNotifier는 Protocol을 상속하지 않아도 send 메서드만 맞으면 정적 검사에서 호환됩니다. 즉, "이 객체가 무엇인가"보다 "무엇을 할 수 있는가"를 선언하는 셈입니다. CLI 실습에서 만든 가짜 노티파이어(Test Double)도 같은 메서드 서명을 지키기만 하면 타입 체커가 통과시킵니다.
구조적 서브타이핑과 runtime_checkable (Optional)
from typing import runtime_checkable
@runtime_checkable
class Exporter(Protocol):
def export(self, data: dict) -> bytes:
...
def run_export(obj: Exporter):
assert isinstance(obj, Exporter)
return obj.export({})
@runtime_checkable을 붙이면 isinstance 검사에도 사용할 수 있습니다. 다만 메서드 존재 여부만 확인하므로, 복잡한 조건이 필요하면 abc.ABC 기반 추상 클래스를 고려하세요.
타입 체커 통합 (Core → Plus)
정적 검사를 한 번도 돌려보지 않았다면 uv run mypy src 한 줄만 성공시키는 것을 1차 목표로 삼으세요. 이후에 CI나 IDE 자동 실행을 붙이면 됩니다.
uv add mypy후mypy src로 정적 검사 실행- VS Code, PyCharm은 저장 시 자동 검사 지원
- CI에서는
mypy --strict플래그로 규칙을 강화할 수 있습니다.
코드 편집기, 정적 검사기, CI를 한 흐름으로 묶어두면 팀원 모두가 같은 타입 계약을 따르게 됩니다.
왜 중요할까
- 타입 힌트는 개발 경험을 개선하는 선택 사항이지만 팀 협업에서 큰 가치를 줍니다.
- Protocol을 사용하면 이름 대신 동작으로 인터페이스를 정의할 수 있습니다.
- 정적 검사 도구를 CI에 연동해 지속적으로 신뢰도를 높일 수 있습니다.
실습
- 따라 하기:
grade와broadcast함수에 타입 힌트를 붙이고mypy를 실행해 통과 여부를 확인합니다. - 확장하기: Protocol을 하나 정의해 Slack/SMS 구현을 바꿔 끼우고 필요하면
TypedDict나runtime_checkable기능을 실험합니다. - 디버깅: 함수 반환 타입을 일부러 잘못 적어
mypy오류를 만든 뒤 정확한 타입으로 고쳐 차이점을 기록합니다. - 완료 기준: 핵심 함수와 Protocol이 타입 체커를 통과하고 선택 확장 섹션을 필요할 때 참고 목록으로 정리했을 때입니다.
마무리
다음 편에서는 Python의 async/await 문법으로 비동기 코드를 작성하는 기본 흐름을 설명합니다.
💬 댓글
이 글에 대한 의견을 남겨주세요