파이썬 타입 힌트와 mypy 완벽 가이드: 정적 타입 검사로 버그 줄이기

파이썬 타입 힌트 기본 문법부터 mypy 설치·실행, 컬렉션과 None 표현, 오류 수정, pyproject.toml 설정과 기존 프로젝트 점진적 도입법까지 설명합니다.

본문 상단 광고 구역 (승인 후 자동 노출됩니다)
파이썬 타입 힌트 코드와 mypy 정적 검사 결과를 보여주는 개발 환경
파이썬 타입 힌트와 mypy 정적 타입 검사 가이드

파이썬 타입 힌트는 변수와 함수가 어떤 자료형을 주고받는지 코드에 표시하는 문법이고, mypy는 그 표시와 실제 사용이 맞는지 프로그램 실행 전에 검사하는 도구입니다. 타입 힌트만 적는다고 런타임에서 자료형이 강제되지는 않지만, mypy를 함께 사용하면 잘못된 인수·반환값·None 처리처럼 실행 후 발견하기 쉬운 오류를 개발 단계에서 줄일 수 있습니다.

mypy 공식 시작 안내 확인하기

핵심 요약

  • 타입 힌트는 코드의 입력과 출력 계약을 사람이 읽고 도구가 분석할 수 있게 표시합니다.
  • mypy는 코드를 실행하지 않고 타입 불일치를 찾아주는 정적 타입 검사기입니다.
  • 처음부터 모든 파일을 엄격하게 검사하기보다 새 코드와 핵심 함수부터 적용하는 편이 현실적입니다.
  • Any를 과도하게 쓰면 검사가 사실상 우회되므로 구체적인 타입을 우선해야 합니다.

타입 힌트와 mypy의 역할

파이썬은 동적 타입 언어이므로 변수에 여러 종류의 값을 담을 수 있습니다. 유연하다는 장점이 있지만, 함수가 문자열을 기대하는지 숫자를 기대하는지 호출하는 쪽에서 놓치기 쉽습니다. 타입 힌트는 이 의도를 name: str, -> int처럼 명시합니다. IDE의 자동완성·리팩터링·경고 품질도 좋아질 수 있습니다.

중요한 점은 Python 공식 문서가 밝히듯 런타임이 함수와 변수의 타입 주석을 자체적으로 강제하지 않는다는 사실입니다. 잘못된 타입을 전달해도 해당 코드 경로에서 실제 오류가 발생하기 전까지 실행될 수 있습니다. mypy는 별도 검사 단계에서 타입 관계를 분석해 이런 불일치를 미리 알려줍니다.

구분 타입 힌트 mypy 런타임 검증
작동 시점 코드 작성·읽기 실행 전 검사 프로그램 실행 중
주요 목적 의도와 계약 표현 정적 타입 오류 발견 실제 입력값 검증
오류 시 그 자체로 중단하지 않음 검사 결과에 오류 표시 예외 처리 또는 실행 중단

API 입력 데이터까지 실행 중에 검증해야 한다면 타입 힌트만으로는 부족합니다. 데이터 파싱과 검증이 목적일 때는 Pydantic처럼 런타임 검증을 수행하는 도구의 역할을 구분해야 합니다.

함수와 변수의 기본 타입 힌트 문법

매개변수 이름 뒤에는 콜론과 타입을 쓰고, 함수의 반환 타입은 화살표 뒤에 씁니다. 반환값이 없는 함수는 None을 표시합니다.

def calculate_total(price: int, quantity: int) -> int:
    return price * quantity

def print_message(message: str) -> None:
    print(message)

user_name: str = "Jin"
retry_count: int = 3

클래스도 같은 방식으로 속성과 메서드의 계약을 표현할 수 있습니다. 생성자 __init__의 반환 타입은 None이며, 인스턴스 메서드의 self에는 보통 타입을 따로 적지 않습니다.

class Product:
    def __init__(self, name: str, price: int) -> None:
        self.name = name
        self.price = price

    def discounted_price(self, rate: float) -> int:
        return int(self.price * (1 - rate))

타입 힌트는 문서 역할도 하므로 이름만 보고 의미를 알 수 있게 작성하는 것이 좋습니다. 객체의 연산·비교 동작까지 설계할 때는 각 매직 메서드가 기대하는 입력과 반환 타입을 함께 표시하면 검사와 유지보수가 쉬워집니다.

컬렉션, 여러 타입, None 표현하기

현대 파이썬에서는 list[str], dict[str, int]처럼 내장 컬렉션에 원소 타입을 함께 표시할 수 있습니다. tuple[int, str]은 위치별 타입을, set[str]은 집합 원소 타입을 나타냅니다. 프로젝트가 지원하는 파이썬 버전에 따라 사용 가능한 문법이 달라질 수 있으므로 실행 환경을 먼저 확인해야 합니다.

names: list[str] = ["Kim", "Lee"]
scores: dict[str, int] = {"Kim": 90, "Lee": 85}

def first_name(names: list[str]) -> str | None:
    if not names:
        return None
    return names[0]

str | None은 문자열 또는 None을 반환할 수 있다는 뜻입니다. 이 값을 바로 .upper()에 넘기면 mypy가 None 가능성을 경고합니다. 먼저 if value is None으로 분기하면 검사기가 이후 구간의 타입을 문자열로 좁혀 판단합니다.

Any는 어떤 타입과도 호환되는 탈출구입니다. 외부 라이브러리나 점진적 전환 과정에서는 필요하지만, 핵심 로직에 넓게 사용하면 잘못된 연산도 통과할 수 있습니다. 어떤 값이든 받을 수 있지만 함부로 연산해서는 안 된다는 뜻이라면 object가 더 안전한 선택일 수 있습니다.

mypy 설치와 첫 검사 실행

프로젝트별 가상환경을 활성화한 뒤 mypy를 설치하면 다른 프로젝트와 의존성이 섞이는 일을 줄일 수 있습니다. 설치 후 파일이나 패키지 경로를 지정해 검사합니다.

python -m pip install mypy
python -m mypy app.py
python -m mypy src

명령이 성공했더라도 모든 런타임 버그가 사라진다는 뜻은 아닙니다. mypy는 타입 계약을 검사하고 pytest 같은 테스트 도구는 실제 입력과 동작 결과를 검증합니다. 두 검사는 대체 관계가 아니라 서로 다른 실패를 찾는 보완 관계입니다.

가상환경과 의존성 파일을 아직 정리하지 않았다면 먼저 실행 환경을 고정하는 편이 좋습니다. 개발자마다 mypy 버전이나 라이브러리 스텁이 달라지면 검사 결과가 어긋날 수 있기 때문입니다.

mypy 오류를 읽고 고치는 순서

예를 들어 반환 타입을 int로 선언한 함수가 문자열을 반환하면 Incompatible return value type 계열의 오류가 표시됩니다. 경고를 없애려고 주석만 실제 코드에 맞추기 전에, 함수의 계약과 구현 중 어느 쪽이 잘못됐는지 먼저 판단해야 합니다.

  1. 오류가 표시된 파일과 줄 번호를 확인합니다.
  2. 함수 선언의 매개변수·반환 타입을 읽습니다.
  3. 호출부에서 실제로 전달하는 값과 모든 반환 경로를 확인합니다.
  4. None 가능성, 컬렉션 원소 타입, 외부 라이브러리 타입 정보 누락을 구분합니다.
  5. 수정 후 같은 범위에 mypy를 다시 실행하고 테스트도 함께 돌립니다.
def get_age(text: str) -> int:
    return text  # mypy가 반환 타입 불일치를 찾음

def get_age_fixed(text: str) -> int:
    return int(text)

마지막 수정은 타입 검사에는 맞지만 text가 숫자가 아니면 실행 중 ValueError가 발생할 수 있습니다. 따라서 타입 검사 통과와 입력값 유효성 검사는 분리해서 생각하고, 실패 가능한 변환에는 적절한 예외 처리를 추가해야 합니다.

pyproject.toml 설정과 기존 프로젝트 도입법

mypy는 명령 옵션뿐 아니라 mypy.ini, .mypy.ini, pyproject.toml, setup.cfg 같은 설정 파일을 탐색합니다. 여러 설정 파일을 동시에 흩어 놓기보다 팀이 사용하는 하나의 파일에 검사 대상과 규칙을 명확히 두는 편이 좋습니다.

[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_ignores = true
disallow_untyped_defs = true
exclude = ["build/", "dist/"]

disallow_untyped_defs는 타입이 없는 함수 정의를 허용하지 않는 엄격한 설정입니다. 오래된 프로젝트에 처음부터 적용하면 오류가 너무 많이 생길 수 있습니다. 새 모듈이나 핵심 도메인부터 검사하고, 수정한 파일의 타입 누락을 금지한 뒤 적용 범위를 넓히는 방식이 효율적입니다.

외부 패키지에 타입 정보가 없을 때 경고를 모두 무시하는 설정부터 추가하면 실제 문제까지 가려질 수 있습니다. 패키지가 타입 스텁을 제공하는지 확인하고, 꼭 필요한 모듈에만 예외를 좁게 적용하십시오. # type: ignore도 이유를 확인한 특정 줄에서만 사용하고 warn_unused_ignores로 불필요해진 무시 주석을 찾아내는 것이 좋습니다.

실수 방지 체크리스트

  • 타입 힌트가 런타임 입력 검증을 대신한다고 오해하지 않았는지 확인합니다.
  • 모든 함수의 반환 경로가 선언한 타입과 일치하는지 확인합니다.
  • 값이 없을 수 있다면 | None을 표시하고 사용 전에 분기합니다.
  • 빈 목록·딕셔너리처럼 추론이 모호한 변수에 원소 타입을 적습니다.
  • Any와 전체 모듈 무시 설정을 최소화합니다.
  • 팀의 파이썬 버전과 mypy 버전을 고정합니다.
  • 정적 검사 후에도 pytest 등으로 실제 동작을 검사합니다.
  • CI에서 같은 명령을 실행해 새 타입 오류의 유입을 막습니다.

자주 묻는 질문

1. 타입 힌트를 쓰면 파이썬이 정적 타입 언어가 되나요?

아닙니다. 파이썬의 동적 실행 특성은 유지됩니다. 타입 힌트는 분석 도구가 활용하는 정보이며 런타임이 자동으로 강제하지 않습니다.

2. mypy 검사를 통과하면 프로그램에 버그가 없나요?

아닙니다. 타입 불일치 가능성을 줄여주지만 계산 로직, 네트워크 실패, 잘못된 요구사항 같은 문제는 테스트와 예외 처리로 별도 확인해야 합니다.

3. mypy는 파이썬 표준 라이브러리인가요?

아닙니다. 별도로 설치해서 사용하는 정적 타입 검사기입니다. 타입 힌트 문법과 typing 모듈은 파이썬에서 제공합니다.

4. 작은 스크립트에도 타입 힌트가 필요한가요?

일회성 코드라면 이점이 작을 수 있지만, 재사용하거나 수정할 가능성이 있는 함수의 입력·출력에는 타입을 표시하면 실수를 줄이는 데 도움이 됩니다.

5. list와 List 중 무엇을 써야 하나요?

현대 파이썬에서는 보통 list[str]처럼 내장 컬렉션 문법을 사용합니다. 다만 지원해야 하는 파이썬 버전과 프로젝트 규칙을 먼저 확인하십시오.

6. Optional[str]과 str | None은 같은 뜻인가요?

둘 다 문자열 또는 None을 표현하는 용도로 사용됩니다. str | None 문법은 Python 3.10 이상에서 사용할 수 있습니다.

7. Any를 쓰면 왜 주의해야 하나요?

Any는 대부분의 타입 검사를 통과하므로 잘못된 속성 접근이나 대입을 놓칠 수 있습니다. 동적 값과의 경계에서 제한적으로 쓰는 편이 좋습니다.

8. 외부 라이브러리 때문에 타입 오류가 나면 어떻게 하나요?

먼저 해당 라이브러리의 타입 정보나 스텁 패키지 제공 여부를 확인하십시오. 무시가 필요하다면 전체 검사를 끄지 말고 대상 모듈이나 줄로 범위를 좁히는 것이 안전합니다.

9. 기존 대형 프로젝트에는 어떻게 시작하나요?

새 코드와 자주 쓰이는 핵심 모듈부터 타입을 추가하고 검사 범위를 단계적으로 넓히십시오. 한꺼번에 엄격 모드를 적용하면 경고가 과도해져 실제 개선이 어려울 수 있습니다.

10. mypy와 pytest 중 무엇을 먼저 실행해야 하나요?

둘 다 자동화하는 것이 좋습니다. 일반적으로 빠른 정적 검사와 단위 테스트를 같은 개발·CI 과정에 넣어 타입 계약과 실제 동작을 함께 확인합니다.

공식 출처

공식 문서 확인일: 2026년 9월 9일. 사용 중인 Python 및 mypy 버전에 따라 지원 문법과 기본 검사 동작이 달라질 수 있으므로 실제 프로젝트 버전의 문서를 함께 확인하시기 바랍니다.

본문 하단 광고 구역