프롤로그 — 2026년, Python에 정적 타입 체커가 너무 많아졌다
Python은 동적 언어로 시작했다. 그런데 2026년 현재, 산업 현장에서 Python을 짠다는 건 사실상 **타입 어노테이션을 짠다는 뜻**이다. FastAPI는 타입 힌트로 라우팅을 정의하고, Pydantic은 타입으로 데이터 검증을 하고, SQLAlchemy 2.0 ORM은 `Mapped[int]`로 컬럼을 선언한다. 타입이 없으면 현대 Python 라이브러리는 쓰기 어렵다.
문제는 **누가 그 타입을 검사하는가**다. 그리고 2025-2026년 사이에 이 질문의 답이 갑자기 복잡해졌다.
- **Mypy** — 2012년부터 있던 표준 구현. Dropbox 출신 Jukka Lehtosalo와 Guido van Rossum이 만들었다.
- **Pyright** — 2019년 Microsoft 발표. VS Code의 Pylance 엔진. IDE 피드백 최강.
- **Pyre** — Meta가 Instagram 모노레포를 위해 만든 OCaml 기반 체커. 점점 쇠퇴.
- **Pyrefly** — 2025년 Meta가 Pyre를 Rust로 다시 쓴 후속작. 모노레포에서 빠르다.
- **ty** — 2025년 Astral(uv·ruff 회사)이 발표한 Rust 기반 체커. 알파지만 빠르다.
- **Pytype** — Google이 만든 Python 2/3 추론 체커. 사용량 감소 중.
이 글은 2026년 5월 현재 이 도구들의 지도를 그린다. 누가 뭘 만들고 있고, soundness·speed·ergonomics가 어떻게 다르고, PEP 695·742 같은 최신 문법을 어디까지 따라가고, 점진적 도입을 어떻게 시작할지까지.
1장 · 2026년 Python 타입 체커 지도
먼저 큰 그림. 각 체커는 만든 곳·구현 언어·주력 사용처가 다르다.
| 체커 | 만든 곳 | 구현 언어 | 주력 사용처 | 2026년 상태 |
| --- | --- | --- | --- | --- |
| Mypy | Dropbox / Python 재단 | Python (compile to C via mypyc) | OSS 표준, 라이브러리 저자 | 안정, 표준 reference |
| Pyright | Microsoft | TypeScript / Node | IDE(Pylance), CI | 매우 활발 |
| Pylance | Microsoft (Pyright 기반) | TypeScript / Node | VS Code 전용 | Pyright의 IDE wrapper |
| Pyre | Meta | OCaml | Instagram 모노레포 | 유지보수 모드 |
| Pyrefly | Meta | Rust | Meta 모노레포·OSS | 2025년 OSS 공개, 빠르게 성장 |
| ty | Astral | Rust | uv 생태계 사용자 | 2025년 알파, 가속 중 |
| Pytype | Google | Python | Google 내부 | 사용량 감소, 유지보수만 |
지도에서 보이는 큰 트렌드 두 가지.
**첫째, Rust 재작성 물결.** Python 도구 체인은 지난 3년간 Rust로 옮겨 갔다. Ruff(린터)·uv(패키지 매니저)가 Astral의 트레이드마크가 됐고, 이제 타입 체커도 Rust로 가는 중이다. ty와 Pyrefly가 그 흐름의 대표. Mypy는 이미 mypyc(Python → C 컴파일러)를 통해 자기 자신을 컴파일하지만, Rust 네이티브에 비하면 여전히 느리다.
**둘째, IDE vs CLI 분리.** Pyright/Pylance는 LSP·incremental·watch 모드에 강점이 있다. Mypy는 batch CI 검사에 강점이 있다. 둘 다 쓰는 팀이 늘었다. ty와 Pyrefly가 이 두 시장을 동시에 노린다.
2장 · Python typing PEP 연대기 — 굵직한 변화
타입 체커를 비교하려면 먼저 **무엇을 체크해야 하는지** 정해진 PEP을 알아야 한다. 굵직한 것만 추렸다.
| PEP | 연도 | 내용 | Python 버전 |
| --- | --- | --- | --- |
| 484 | 2014 | 타입 힌트 도입 | 3.5 |
| 526 | 2016 | 변수 어노테이션 | 3.6 |
| 544 | 2017 | Protocol(구조적 부분 타이핑) | 3.8 |
| 561 | 2017 | 배포 시 타입 정보(py.typed) | 3.7 |
| 585 | 2019 | builtin 제네릭(`list[int]`) | 3.9 |
| 591 | 2019 | Final 한정자 | 3.8 |
| 593 | 2019 | Annotated | 3.9 |
| 604 | 2019 | `X` 파이프 `Y` 유니온 표기 | 3.10 |
| 612 | 2019 | ParamSpec | 3.10 |
| 646 | 2020 | TypeVarTuple | 3.11 |
| 647 | 2021 | TypeGuard | 3.10 |
| 673 | 2022 | Self 타입 | 3.11 |
| 675 | 2022 | LiteralString | 3.11 |
| 681 | 2022 | dataclass_transform | 3.11 |
| 692 | 2023 | TypedDict로 kwargs 타이핑 | 3.12 |
| 695 | 2022 | 새 제네릭 문법(`class Box[T]:`) | 3.12 |
| 696 | 2022 | TypeVar 기본값 | 3.13 |
| 698 | 2022 | `@override` 데코레이터 | 3.12 |
| 702 | 2023 | `@deprecated` 데코레이터 | 3.13 |
| 705 | 2023 | TypedDict readonly | 3.13 |
| 712 | 2023 | dataclass 변환 fields | (제안) |
| 728 | 2023 | TypedDict closed/extra_items | (제안) |
| 738 | 2024 | 새 PyOdide 관련(typing 외) | n/a |
| 742 | 2024 | TypeIs(향상된 narrowing) | 3.13 |
| 749 | 2024 | 어노테이션 lazy evaluation 명세 | (논의) |
| 750 | 2024 | t-string(typed string literal) | (제안) |
핵심 정리.
- **PEP 695 (3.12)**: 새 제네릭 문법이 들어왔다. `class Box[T]:` 처럼 클래스 옆에 직접 타입 매개변수를 적는다. TypeScript와 비슷해졌다.
- **PEP 696 (3.13)**: TypeVar 기본값. `Box[T = int]` 같은 표기가 가능.
- **PEP 742 (3.13)**: TypeGuard의 약점을 보완한 TypeIs. narrowing이 양방향으로 작동한다.
- **PEP 698 (3.12)**: `@override` 데코레이터. 메서드 오버라이드 검증.
- **PEP 749**: 어노테이션 평가 방식 정리. `from __future__ import annotations` 그 다음을 정의.
각 체커는 이 PEP 목록을 따라잡는 속도가 다르다. **Pyright가 가장 빠르고, Mypy가 가장 느리다.** ty와 Pyrefly는 신규 PEP을 처음부터 구현한다.
3장 · Mypy — 표준 reference 구현체
Mypy는 2012년에 시작했다. Jukka Lehtosalo의 박사 논문에서 출발해, Guido van Rossum이 Dropbox에서 합류한 뒤 본격화됐다. 2026년 현재 PEP 484의 사실상 reference 구현이다.
mypy의 검사 예제
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
if user_id < 0:
return None
return "alice"
name = find_user(42)
print(name.upper()) # mypy 오류: name이 None일 수 있음
mypy를 돌리면 위 코드는 다음과 같이 잡힌다.
example.py:9: error: Item "None" of "Optional[str]" has no attribute "upper"
Found 1 error in 1 file (checked 1 source file)
**Mypy의 장점.**
1. **표준 동작.** typing 모듈 PEP의 reference 구현. 다른 체커는 mypy의 행동을 기준으로 비교당한다.
2. **풍부한 플러그인.** Django·SQLAlchemy·attrs 등 동적 동작이 많은 라이브러리용 플러그인 시스템이 잘 정립돼 있다.
3. **incremental cache.** `.mypy_cache/` 디렉토리에 결과를 저장해 두 번째 실행부터 빨라진다.
4. **mypyc로 자체 컴파일.** mypy 자체를 mypyc로 C 익스텐션화해 속도를 끌어올렸다.
**Mypy의 약점.**
1. **속도.** 모노레포에서 cold start가 느리다. 50만 줄 코드베이스에서 3-5분이 흔하다.
2. **신규 PEP 따라잡기.** PEP 695를 한참 만에 받았고, PEP 742(TypeIs)도 늦었다.
3. **에러 메시지.** "Argument 2 to ... has incompatible type" 같은 문구가 길고 난해할 때가 있다.
전형적인 `mypy.ini` 설정.
[mypy]
python_version = 3.13
strict = True
plugins = pydantic.mypy, sqlalchemy.ext.mypy.plugin
[mypy-tests.*]
disallow_untyped_defs = False
`strict = True`는 사실상 "모든 hardness를 켠다"는 의미다. 신규 코드는 strict로 시작하고, 레거시 코드는 모듈별로 풀어 주는 게 정석이다.
4장 · Pyright + Pylance — Microsoft, IDE 피드백 최강
Pyright는 2019년 Microsoft가 발표했다. TypeScript로 짜여졌고, Node.js에서 돈다. VS Code의 Python 확장 안에 Pylance라는 이름으로 들어가 있다.
Pyright의 설계 목표는 **incremental·즉각·정확**이다. 파일 하나 저장하면 0.1초 안에 결과가 나와야 한다. 이걸 위해 Pyright는 다음을 한다.
- **AST 캐싱.** 변경되지 않은 파일은 다시 파싱하지 않음.
- **Symbol 단위 incremental.** 함수 한 개만 바뀌면 그 함수의 의존성만 재검사.
- **inference의 적극성.** Mypy보다 더 적극적으로 타입을 추론한다(특히 type narrowing).
Pyright의 narrowing 예제
from typing import Union
def process(x: Union[int, str]) -> str:
if isinstance(x, int):
여기서 x는 int로 narrowing됨 — Mypy도 됨
return str(x * 2)
여기서 x는 str — Mypy도 됨
return x.upper()
기본 narrowing은 Mypy도 한다. 차이는 더 미묘한 경우에 나타난다.
Pyright는 narrowing하지만 Mypy는 못 하는 경우가 있다
from typing import Literal
def get_status() -> Literal["ok", "error"]:
...
s = get_status()
if s == "ok":
Pyright: s는 Literal["ok"]
Mypy: 같음 (둘 다 OK)
handle_ok()
**Pylance vs Pyright의 차이.**
- **Pyright**: 오픈소스. CLI·LSP·CI 어디서나 쓸 수 있다.
- **Pylance**: VS Code 전용 클로즈드 소스. Pyright 엔진 + VS Code 통합(자동 import·docstring 인레이·세션 캐시 등).
Pyright는 npm으로 받는다.
npm install -g pyright
pyright src/
설정은 `pyrightconfig.json` 또는 `pyproject.toml`의 `[tool.pyright]` 섹션.
{
"include": ["src"],
"exclude": ["**/node_modules", "**/__pycache__"],
"typeCheckingMode": "strict",
"pythonVersion": "3.13",
"reportMissingImports": "error",
"reportUnknownArgumentType": "warning"
}
`typeCheckingMode`는 `off`·`basic`·`standard`·`strict` 네 단계. `strict`는 Mypy의 `--strict`보다 더 빡빡한 경향이 있다.
5장 · Astral의 `ty` — Rust 기반, ruff·uv 만든 팀
Astral은 2024-2025년 사이 Python 도구 체인을 다시 썼다. Ruff(린터)·uv(패키지 매니저)가 압도적으로 빠르다는 게 입증된 뒤, 2025년 같은 팀이 **ty**라는 타입 체커를 알파로 공개했다.
ty의 설계 원칙.
1. **Rust 네이티브.** Mypy의 30-100배 빠른 cold start가 목표.
2. **incremental DB.** Salsa(rust-analyzer가 쓰는 incremental computation 프레임워크) 기반.
3. **LSP·CLI 양쪽.** Pyright처럼 IDE 통합이 일급 시민.
4. **ruff와 통합.** 같은 워크스페이스 설정·같은 캐시 디렉토리.
2026년 5월 현재 ty는 알파 단계지만, **신규 PEP 구현이 빠르다**. PEP 695는 출시 시점부터 지원했고, PEP 742·749도 이미 들어가 있다.
설치
uv tool install ty
검사
ty check src/
LSP 서버 띄우기
ty server
설정은 `pyproject.toml`의 `[tool.ty]` 섹션.
[tool.ty]
python-version = "3.13"
strict = true
exclude = ["tests/"]
[tool.ty.rules]
unresolved-import = "error"
unused-ignore-comment = "warning"
**ty가 던지는 위협.**
- 속도가 압도적. 50만 줄 모노레포에서 Mypy 3분 → ty 5초 같은 보고가 알파 단계에서 이미 나온다.
- ruff·uv 사용자에게는 도구 체인 통일의 매력. 셋 다 같은 회사·같은 캐시·같은 컨피그.
- 단, **plugin 시스템이 아직 없다**. SQLAlchemy 2.0·Pydantic 2 같은 동적 라이브러리는 정적 분석만으로 잡기 어려운 부분이 있다.
**2026년 5월의 현실.** ty는 신규 프로젝트나 작은 라이브러리에 좋다. 5만 줄 이상 SQLAlchemy 1.x 코드베이스를 ty로 검사하면 false positive가 쏟아진다. 1년 안에 안정화될 거라는 게 일반적 관측.
6장 · Meta `Pyrefly` — Pyre의 Rust 재작성
Meta는 Instagram·Facebook 백엔드에 1억 줄 단위의 Python 모노레포를 운영한다. Pyre는 이 모노레포를 위해 만든 OCaml 기반 체커였다. 그런데 OCaml 생태계가 점점 좁아지고, 모노레포의 incremental·distributed 요구가 커지자 Meta는 2024-2025년에 **Pyrefly**라는 Rust 재작성 후속작을 만들었다.
Pyrefly의 강점.
1. **모노레포 친화.** 분산 인덱싱·증분 검사·심볼 DB가 모노레포 스케일에서 검증됐다.
2. **type at scale.** 1억 줄에서 돈다는 게 메타 모노레포 내부에서 입증.
3. **OSS 공개.** 2025년에 OSS로 풀렸다. 메타 외부에서도 쓸 수 있다.
설치
cargo install pyrefly
또는
pip install pyrefly
검사
pyrefly check src/
Pyrefly의 설정은 `pyrefly.toml` 또는 `pyproject.toml`의 `[tool.pyrefly]`.
[tool.pyrefly]
python_version = "3.13"
project_root = "."
search_path = ["src", "lib"]
strict = true
**Pyrefly vs ty의 포지셔닝 차이.**
- **Pyrefly**: 모노레포 스케일이 먼저. Meta 내부 검증이 강점.
- **ty**: 일반 OSS·중소규모 프로젝트가 먼저. ruff·uv 생태계 통합이 강점.
둘 다 Rust지만 타깃 시장이 다르다. 작은 라이브러리 저자는 ty, Meta 같은 모노레포 운영자는 Pyrefly를 선호하는 패턴이 보인다.
7장 · Google `Pytype` — 사용량 감소하지만 여전
Pytype은 Google이 만들었다. 2015-2017년 사이 활발했고, Python 2/3 추론에 강점이 있었다. **타입 어노테이션 없이도 타입을 추론한다**는 게 차별점이었다.
Pytype은 어노테이션 없이도 추론한다
def add(x, y):
return x + y
Pytype: 사용처를 추적해 int·str·float 등의 가능성을 추정
add(1, 2) # int + int → int
add("a", "b") # str + str → str
**Pytype의 강점.**
1. 어노테이션이 없는 레거시 코드에 대한 추론 능력.
2. `.pyi` 스텁 파일을 자동 생성.
3. Google 내부에서 오래 검증.
**Pytype의 약점.**
1. **사용량 감소.** Google 내부에서도 Pyright·Pyrefly로 옮겨 가는 팀이 있다.
2. **신규 PEP 따라잡기.** PEP 695·742 같은 신규 문법 지원이 늦다.
3. **속도.** 모노레포에서 Mypy보다도 느리다는 보고가 있다.
2026년 현재 Pytype은 **신규 도입 추천 대상은 아니다**. 다만 Google·Apache Beam 같은 일부 대형 프로젝트가 여전히 쓰고 있어 완전히 사라지진 않을 것이다.
8장 · Soundness vs Speed vs Ergonomics — 4종 비교 표
타입 체커를 고를 때 보는 3개 축.
- **Soundness**: 잡아낼 수 있는 버그의 폭. "Mypy가 통과시키고 런타임에서 깨지는 코드"가 얼마나 적은가.
- **Speed**: cold start와 incremental 모두.
- **Ergonomics**: 에러 메시지·플러그인·LSP·문서.
2026년 5월 기준 주관적 평가.
| 체커 | Soundness | Cold Start 속도 | Incremental 속도 | LSP | 에러 메시지 | Plugin |
| --- | --- | --- | --- | --- | --- | --- |
| Mypy | 표준 | 느림 | 보통 | 별도 daemon 필요 | 길고 난해 | 풍부 |
| Pyright | 매우 강함 | 빠름 | 매우 빠름 | 일급 시민 | 짧고 명확 | 제한적 |
| Pyrefly | 강함 | 매우 빠름 | 매우 빠름 | 일급 시민 | 명확 | 미완성 |
| ty | 미완성 | 매우 빠름 | 매우 빠름 | 일급 시민 | 명확 | 없음 |
**이론적 soundness**는 Mypy가 표준이고 다른 체커는 그걸 reference로 친다. **실전 soundness**는 narrowing·overload·protocol 같은 미묘한 경우에서 갈린다.
**속도**는 Pyrefly·ty가 압도. 100만 줄 코드베이스에서 Mypy 5분 → Pyright 30초 → Pyrefly·ty 5초.
**에러 메시지**는 Pyright가 최고. Mypy는 길고, Pyrefly·ty는 명확하지만 초기 단계.
**Plugin**은 Mypy가 압도. 단, 신규 도구가 Pydantic 2·SQLAlchemy 2.0 같은 신규 라이브러리를 처음부터 잘 지원한다는 점은 흥미롭다.
9장 · PEP 695·TypeIs·Self·Never — 실전에서 자주 만나는 것들
2026년 Python 코드를 보면 PEP 484 시절과 문법이 많이 달라졌다. 자주 마주치는 것들.
PEP 695 — 새 제네릭 문법 (3.12+)
옛 문법(여전히 동작):
from typing import TypeVar, Generic
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, item: T) -> None:
self.item = item
새 문법(3.12+):
class Box[T]:
def __init__(self, item: T) -> None:
self.item = item
함수도 가능
def first[T](items: list[T]) -> T:
return items[0]
타입 별칭도
type StringList = list[str]
TypeScript와 비슷한 모양이 됐다. Pyright는 처음부터 지원했고, Mypy는 1.4부터 부분 지원, 1.7부터 안정. ty·Pyrefly는 출시 시점부터 지원.
PEP 742 — TypeIs (3.13+)
TypeGuard의 약점을 보완한 narrowing.
from typing import TypeIs
def is_str_list(val: list[object]) -> TypeIs[list[str]]:
return all(isinstance(x, str) for x in val)
def process(items: list[object]) -> None:
if is_str_list(items):
items는 list[str]로 narrowing
print(",".join(items))
else:
items는 여전히 list[object] (TypeGuard에서는 못 했던 일)
print("not strings")
`TypeIs`와 `TypeGuard`의 차이: `TypeIs`는 **양방향**으로 narrowing한다. else 분기에서도 "그것이 아닌 타입"이 살아남는다.
Self 타입 (PEP 673, 3.11+)
from typing import Self
class Builder:
def with_name(self, name: str) -> Self:
self.name = name
return self
def with_age(self, age: int) -> Self:
self.age = age
return self
class FancyBuilder(Builder):
def with_color(self, color: str) -> Self:
self.color = color
return self
Self 덕분에 체이닝이 서브클래스 타입을 유지
b = FancyBuilder().with_name("alice").with_color("red")
b: FancyBuilder (Self 덕에 Builder가 아님)
이전엔 `TypeVar` 트릭으로 했던 것을 `Self` 한 단어로 해결.
Never 타입
from typing import Never
def fail(msg: str) -> Never:
raise RuntimeError(msg)
def process(x: int | str) -> str:
if isinstance(x, int):
return str(x)
if isinstance(x, str):
return x
fail("unreachable") # 이 후 코드는 dead code로 표시됨
`Never`는 "이 함수는 반환하지 않는다"는 명시. 컴파일러가 dead code 분석에 활용.
`@override` 데코레이터 (PEP 698, 3.12+)
from typing import override
class Base:
def greet(self) -> str:
return "hello"
class Child(Base):
@override
def greet(self) -> str:
return "hi"
@override
def greeet(self) -> str: # 오타 — 부모에 없음, 체커가 잡음
return "hi"
TypeScript의 `override` 키워드와 같은 역할. 부모 클래스에 같은 이름의 메서드가 없으면 에러.
10장 · strict 모드와 점진적 타입 도입 전략
처음부터 strict로 시작할 수 있으면 좋지만, 기존 코드베이스는 그게 안 된다. 점진적 도입의 정석 패턴.
단계 1 — baseline 잡기
먼저 현재 상태에서 어떤 에러가 있는지 본다.
mypy --ignore-missing-imports src/ > baseline.txt
wc -l baseline.txt
이게 출발점. 새 PR은 이보다 늘리지 않는다는 규칙으로 시작.
단계 2 — 신규 코드는 strict
`mypy.ini`에서 새 모듈에만 strict를 적용.
[mypy]
python_version = 3.13
[mypy-myapp.new_module.*]
strict = True
[mypy-myapp.legacy.*]
ignore_errors = True
신규 코드는 처음부터 타이트하게. 레거시는 일단 무시.
단계 3 — `--strict-optional`만 먼저
`Optional[X]` vs `X`를 구분하는 게 가장 큰 가치를 준다. 다른 strict 플래그보다 먼저 켠다.
[mypy]
strict_optional = True
no_implicit_optional = True
이 두 개만 켜도 NPE 같은 None 관련 버그의 80%는 잡힌다.
단계 4 — `disallow_untyped_defs`
타입 없는 함수 정의를 금지.
[mypy]
disallow_untyped_defs = True
disallow_incomplete_defs = True
새 함수는 무조건 타입을 쓰게 강제. 기존 함수는 점진적으로 추가.
단계 5 — `strict = True`
모든 strict 플래그를 한 번에. 최종 단계.
[mypy]
strict = True
`strict = True`가 켜는 플래그.
- `disallow_untyped_defs`
- `disallow_any_generics`
- `disallow_untyped_calls`
- `disallow_incomplete_defs`
- `disallow_untyped_decorators`
- `check_untyped_defs`
- `no_implicit_optional`
- `warn_redundant_casts`
- `warn_return_any`
- `warn_unused_ignores`
- `strict_equality`
11장 · 실전 셋업 — pre-commit·CI·pytest 통합
pre-commit 훅
`.pre-commit-config.yaml`:
repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
- id: mypy
additional_dependencies:
- "pydantic>=2.0"
- "sqlalchemy>=2.0"
args: ["--strict"]
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.380
hooks:
- id: pyright
두 체커를 같이 돌리는 패턴이 흔하다. Mypy는 깊이, Pyright는 속도.
GitHub Actions CI
`.github/workflows/typecheck.yml`:
name: typecheck
on: [pull_request]
jobs:
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: pip install -e ".[dev]"
- run: mypy --strict src/
pyright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: pip install -e ".[dev]"
- run: pip install pyright
- run: pyright src/
pytest-mypy-plugins — 타입 자체를 테스트
타입 자체가 의도대로 작동하는지를 테스트하는 도구.
tests/typing/test_types.yml
- case: dict_lookup
main: |
from typing import TypedDict
class User(TypedDict):
name: str
age: int
user: User = {"name": "alice", "age": 30}
reveal_type(user["name"]) # N: Revealed type is "builtins.str"
reveal_type(user["age"]) # N: Revealed type is "builtins.int"
이런 yaml을 두면 pytest가 mypy를 돌려 `reveal_type` 출력이 기대값과 같은지 검증.
inline_snapshot — 타입 출력의 snapshot 테스트
`reveal_type` 출력을 snapshot으로 비교.
from inline_snapshot import snapshot
def test_types():
from mymodule import process
result = process(42)
assert type(result) == snapshot(int)
타입 변경이 의도된 것인지 의도되지 않은 것인지 PR diff에서 보임.
mypy daemon (dmypy)
대형 코드베이스에서 incremental 속도를 끌어올리는 방법.
데몬 시작
dmypy start -- --strict
검사 (캐시 살아있음)
dmypy check src/
상태 확인
dmypy status
종료
dmypy stop
VS Code에서 mypy를 쓰면 자동으로 dmypy를 띄우는 확장도 있다. Pyright의 LSP에 비하면 여전히 느리지만, batch mypy보다는 훨씬 빠르다.
12장 · 의사결정 가이드 — 우리 팀은 뭘 써야 하나
상황별 추천.
**Case 1 — 신규 OSS 라이브러리.**
- 메인: Pyright (CI에서)
- 보조: Mypy (배포 시 plugin 호환성 검증)
- 이유: Pyright가 빠르고 강하지만, OSS 사용자는 Mypy로 검사하니까 둘 다 통과해야 한다.
**Case 2 — Pydantic·FastAPI 백엔드.**
- 메인: Pyright + Pylance(VS Code)
- 보조: Mypy with pydantic.mypy plugin
- 이유: Pydantic 2는 Pyright와 잘 맞는다. Mypy는 plugin 필요.
**Case 3 — SQLAlchemy 2.0 + Django 모노레포.**
- 메인: Mypy with sqlalchemy.ext.mypy.plugin, django-stubs
- 보조: Pyright (IDE에서만)
- 이유: SQLAlchemy 1.x → 2.0 마이그레이션은 Mypy plugin이 핵심. Pyright는 일부 ORM 패턴을 못 잡는다.
**Case 4 — 신규 프로젝트, Astral 도구 체인.**
- 메인: ty
- 보조: Pyright (CI 보강)
- 이유: ruff·uv를 이미 쓰면 ty와의 통합이 자연스럽다. 알파지만 신규 프로젝트라면 false positive가 적다.
**Case 5 — 1억 줄 모노레포.**
- 메인: Pyrefly
- 보조: 없음
- 이유: Meta가 이 스케일에서 검증. 다른 체커는 OOM이 난다.
**Case 6 — 레거시 Python 2/3 혼합 코드.**
- 메인: Pytype (당분간만)
- 마이그레이션 후 Mypy 또는 Pyright로 이동
- 이유: Pytype은 어노테이션 없는 코드 추론이 강하다. 단, 신규 도입 추천은 아님.
13장 · 2026년 예측 — 1년 후 누가 살아남나
마지막으로 1년 후를 예측한다.
1. **Pyright는 IDE에서 표준을 유지한다.** Pylance의 VS Code 점유율이 이미 압도. 적이 없다.
2. **Mypy는 reference로 살아남는다.** Plugin 생태계가 너무 큰다. 죽지 않는다.
3. **ty가 가장 빠르게 성장한다.** Astral의 트랙 레코드(ruff·uv)가 신뢰를 주고, alpha → beta → stable이 1년 안에 일어날 가능성 높다.
4. **Pyrefly는 모노레포 전용 도구로 자리잡는다.** OSS에 풀렸지만 ty와 일반 시장은 겹치지 않는다.
5. **Pytype은 유지보수 모드로 머무른다.** 신규 도입 거의 없음.
6. **Pyre는 사실상 종료된다.** Pyrefly로의 완전 이전 발표가 1년 안에 나올 가능성.
가장 큰 와일드카드는 **ty의 plugin 시스템**이다. SQLAlchemy 2.0·Django 같은 동적 라이브러리를 ty가 처음부터 잘 잡으면 Mypy의 마지막 해자가 무너진다. 아니면 ty와 Mypy가 plugin 호환 레이어를 공유할 수도 있다. 어느 쪽이든 2027년 5월에는 풍경이 또 바뀔 것이다.
14장 · 참고 / References
공식 문서
- [Mypy documentation](https://mypy.readthedocs.io/)
- [Pyright documentation](https://microsoft.github.io/pyright/)
- [Pylance — VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance)
- [Pyre](https://pyre-check.org/)
- [Pyrefly (Meta)](https://github.com/facebook/pyrefly)
- [ty (Astral)](https://github.com/astral-sh/ty)
- [Pytype (Google)](https://github.com/google/pytype)
Typing PEP 모음
- [PEP 484 — Type Hints](https://peps.python.org/pep-0484/)
- [PEP 526 — Variable Annotations](https://peps.python.org/pep-0526/)
- [PEP 544 — Protocols](https://peps.python.org/pep-0544/)
- [PEP 561 — Distributing and Packaging Type Information](https://peps.python.org/pep-0561/)
- [PEP 585 — Builtin Generic Types](https://peps.python.org/pep-0585/)
- [PEP 604 — Union Operator](https://peps.python.org/pep-0604/)
- [PEP 612 — ParamSpec](https://peps.python.org/pep-0612/)
- [PEP 646 — TypeVarTuple](https://peps.python.org/pep-0646/)
- [PEP 673 — Self Type](https://peps.python.org/pep-0673/)
- [PEP 681 — dataclass_transform](https://peps.python.org/pep-0681/)
- [PEP 695 — Type Parameter Syntax](https://peps.python.org/pep-0695/)
- [PEP 696 — TypeVar Defaults](https://peps.python.org/pep-0696/)
- [PEP 698 — Override Decorator](https://peps.python.org/pep-0698/)
- [PEP 702 — Deprecated Decorator](https://peps.python.org/pep-0702/)
- [PEP 705 — TypedDict ReadOnly](https://peps.python.org/pep-0705/)
- [PEP 742 — TypeIs](https://peps.python.org/pep-0742/)
- [PEP 749 — Annotation Evaluation](https://peps.python.org/pep-0749/)
실전 도구
- [pytest-mypy-plugins](https://github.com/typeddjango/pytest-mypy-plugins)
- [pydantic.mypy plugin](https://docs.pydantic.dev/latest/integrations/mypy/)
- [sqlalchemy.ext.mypy.plugin](https://docs.sqlalchemy.org/en/20/orm/extensions/mypy.html)
- [django-stubs](https://github.com/typeddjango/django-stubs)
- [typeshed (표준 라이브러리 스텁)](https://github.com/python/typeshed)
배경 글
- [Python typing summit notes](https://github.com/python/typing/discussions)
- [Astral blog](https://astral.sh/blog)
- [Microsoft Pylance changelog](https://github.com/microsoft/pylance-release/blob/main/CHANGELOG.md)
현재 단락 (1/415)
Python은 동적 언어로 시작했다. 그런데 2026년 현재, 산업 현장에서 Python을 짠다는 건 사실상 **타입 어노테이션을 짠다는 뜻**이다. FastAPI는 타입 힌트로 ...