Python 타입 힌트와 mypy로 런타임 에러 줄이기
배경
6개월 전부터 유지보수하고 있는 Flask 기반 API 서버에서 타입 관련 런타임 에러가 자주 발생했다. None 체크 누락, 잘못된 타입의 인자 전달 등이 주된 원인이었다. Python 3.8을 사용하고 있었고, 타입 힌트를 본격적으로 도입하기로 결정했다.
점진적 도입 전략
전체 코드베이스에 한 번에 적용하는 것은 무리였다. 다음 순서로 진행했다.
- 새로 작성하는 함수부터 타입 힌트 적용
- 자주 수정되는 모듈부터 기존 코드에 추가
- mypy 설정을 느슨하게 시작 (
--ignore-missing-imports)
from typing import Optional, List, Dict
def get_user_orders(user_id: int, status: Optional[str] = None) -> List[Dict[str, any]]:
query = Order.query.filter_by(user_id=user_id)
if status:
query = query.filter_by(status=status)
return [order.to_dict() for order in query.all()]
mypy 적용
CI 파이프라인에 mypy 체크를 추가했다. 처음엔 워닝만 표시하고 빌드는 통과시켰다.
# mypy.ini
[mypy]
python_version = 3.8
warn_return_any = True
warn_unused_configs = True
ignore_missing_imports = True
발견한 문제들
타입 체크를 돌리니 예상치 못한 버그들이 드러났다.
- API 응답을 파싱할 때
None처리 누락 - 문자열을 받아야 하는데 정수를 전달하는 케이스
- Optional 타입인데 None 체크 없이 메서드 호출
특히 외부 API 연동 부분에서 응답 구조가 변경되었는데 타입 힌트 덕분에 사전에 파악할 수 있었다.
결과
3개월간 점진적으로 적용한 결과, 주요 모듈의 약 70%에 타입 힌트가 추가되었다. 런타임 타입 에러는 이전 대비 40% 가량 감소했고, 코드 리뷰 시간도 단축되었다. IDE의 자동완성도 훨씬 정확해져서 개발 생산성이 올랐다.
완벽한 타입 커버리지를 목표로 하기보다는, 실용적인 수준에서 점진적으로 개선하는 것이 중요하다고 느꼈다.