Split View: Python 타입 체커 2026 — Mypy·Pyright·Pylance·Pyre·Pyrefly·ty 비교 심층 가이드
Python 타입 체커 2026 — Mypy·Pyright·Pylance·Pyre·Pyrefly·ty 비교 심층 가이드
프롤로그 — 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 | 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의 장점.
- 표준 동작. typing 모듈 PEP의 reference 구현. 다른 체커는 mypy의 행동을 기준으로 비교당한다.
- 풍부한 플러그인. Django·SQLAlchemy·attrs 등 동적 동작이 많은 라이브러리용 플러그인 시스템이 잘 정립돼 있다.
- incremental cache.
.mypy_cache/디렉토리에 결과를 저장해 두 번째 실행부터 빨라진다. - mypyc로 자체 컴파일. mypy 자체를 mypyc로 C 익스텐션화해 속도를 끌어올렸다.
Mypy의 약점.
- 속도. 모노레포에서 cold start가 느리다. 50만 줄 코드베이스에서 3-5분이 흔하다.
- 신규 PEP 따라잡기. PEP 695를 한참 만에 받았고, PEP 742(TypeIs)도 늦었다.
- 에러 메시지. "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의 설계 원칙.
- Rust 네이티브. Mypy의 30-100배 빠른 cold start가 목표.
- incremental DB. Salsa(rust-analyzer가 쓰는 incremental computation 프레임워크) 기반.
- LSP·CLI 양쪽. Pyright처럼 IDE 통합이 일급 시민.
- 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의 강점.
- 모노레포 친화. 분산 인덱싱·증분 검사·심볼 DB가 모노레포 스케일에서 검증됐다.
- type at scale. 1억 줄에서 돈다는 게 메타 모노레포 내부에서 입증.
- 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의 강점.
- 어노테이션이 없는 레거시 코드에 대한 추론 능력.
.pyi스텁 파일을 자동 생성.- Google 내부에서 오래 검증.
Pytype의 약점.
- 사용량 감소. Google 내부에서도 Pyright·Pyrefly로 옮겨 가는 팀이 있다.
- 신규 PEP 따라잡기. PEP 695·742 같은 신규 문법 지원이 늦다.
- 속도. 모노레포에서 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_defsdisallow_any_genericsdisallow_untyped_callsdisallow_incomplete_defsdisallow_untyped_decoratorscheck_untyped_defsno_implicit_optionalwarn_redundant_castswarn_return_anywarn_unused_ignoresstrict_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년 후를 예측한다.
- Pyright는 IDE에서 표준을 유지한다. Pylance의 VS Code 점유율이 이미 압도. 적이 없다.
- Mypy는 reference로 살아남는다. Plugin 생태계가 너무 큰다. 죽지 않는다.
- ty가 가장 빠르게 성장한다. Astral의 트랙 레코드(ruff·uv)가 신뢰를 주고, alpha → beta → stable이 1년 안에 일어날 가능성 높다.
- Pyrefly는 모노레포 전용 도구로 자리잡는다. OSS에 풀렸지만 ty와 일반 시장은 겹치지 않는다.
- Pytype은 유지보수 모드로 머무른다. 신규 도입 거의 없음.
- Pyre는 사실상 종료된다. Pyrefly로의 완전 이전 발표가 1년 안에 나올 가능성.
가장 큰 와일드카드는 ty의 plugin 시스템이다. SQLAlchemy 2.0·Django 같은 동적 라이브러리를 ty가 처음부터 잘 잡으면 Mypy의 마지막 해자가 무너진다. 아니면 ty와 Mypy가 plugin 호환 레이어를 공유할 수도 있다. 어느 쪽이든 2027년 5월에는 풍경이 또 바뀔 것이다.
14장 · 참고 / References
공식 문서
- Mypy documentation
- Pyright documentation
- Pylance — VS Code Marketplace
- Pyre
- Pyrefly (Meta)
- ty (Astral)
- Pytype (Google)
Typing PEP 모음
- PEP 484 — Type Hints
- PEP 526 — Variable Annotations
- PEP 544 — Protocols
- PEP 561 — Distributing and Packaging Type Information
- PEP 585 — Builtin Generic Types
- PEP 604 — Union Operator
- PEP 612 — ParamSpec
- PEP 646 — TypeVarTuple
- PEP 673 — Self Type
- PEP 681 — dataclass_transform
- PEP 695 — Type Parameter Syntax
- PEP 696 — TypeVar Defaults
- PEP 698 — Override Decorator
- PEP 702 — Deprecated Decorator
- PEP 705 — TypedDict ReadOnly
- PEP 742 — TypeIs
- PEP 749 — Annotation Evaluation
실전 도구
- pytest-mypy-plugins
- pydantic.mypy plugin
- sqlalchemy.ext.mypy.plugin
- django-stubs
- typeshed (표준 라이브러리 스텁)
배경 글
Python Type Checkers in 2026 — Mypy, Pyright (Pylance), Pyre, Pyrefly, ty: Deep-Dive Comparison
Prologue — Python has too many type checkers in 2026
Python started as a dynamic language. But in 2026, writing Python in industry effectively means writing type annotations. FastAPI defines routes through type hints. Pydantic validates data through types. SQLAlchemy 2.0's ORM declares columns with Mapped[int]. Modern Python libraries are hard to use without types.
The question is: who checks those types? And between 2025 and 2026, the answer to that question got suddenly complicated.
- Mypy — the reference implementation since 2012. Built by Jukka Lehtosalo (originally his PhD work) with Guido van Rossum at Dropbox.
- Pyright — announced in 2019 by Microsoft. The engine behind Pylance in VS Code. Strongest IDE feedback.
- Pyre — Meta's OCaml-based checker, built for the Instagram monorepo. In decline.
- Pyrefly — 2025 Rust rewrite of Pyre. Fast at monorepo scale.
- ty — 2025 Rust-based checker from Astral (the uv and ruff company). Alpha, but fast.
- Pytype — Google's inference-heavy checker. Declining usage.
This post maps these tools as of May 2026: who builds what, how soundness vs speed vs ergonomics trade off, which PEPs each tool tracks, and how to roll out type checking gradually on a real codebase.
1. The 2026 Python type-checker map
Big picture first. Each checker differs in origin, implementation language, and primary target.
| Checker | Origin | Implementation | Primary target | 2026 state |
|---|---|---|---|---|
| Mypy | Dropbox / Python foundation | Python (compiled to C via mypyc) | OSS standard, library authors | Stable, reference |
| Pyright | Microsoft | TypeScript / Node | IDE (Pylance), CI | Very active |
| Pylance | Microsoft (Pyright-based) | TypeScript / Node | VS Code only | Pyright IDE wrapper |
| Pyre | Meta | OCaml | Instagram monorepo | Maintenance mode |
| Pyrefly | Meta | Rust | Meta monorepo, OSS | OSS in 2025, fast-growing |
| ty | Astral | Rust | uv ecosystem users | Alpha as of 2025, accelerating |
| Pytype | Python | Google internal | Declining, maintenance only |
Two big trends pop out.
First, the Rust rewrite wave. The Python toolchain has been moving to Rust for three years. Ruff (linter) and uv (package manager) made Astral's reputation. Now type checkers are following. ty and Pyrefly are the examples. Mypy already self-compiles via mypyc (a Python-to-C compiler), but Rust-native is still faster.
Second, the IDE vs CLI split. Pyright and Pylance shine at LSP, incremental checking, and watch mode. Mypy shines at batch CI checking. Many teams now use both. ty and Pyrefly aim at both markets simultaneously.
2. Python typing PEP timeline — the big changes
To compare type checkers you first need to know what they should check. Here are the major PEPs.
| PEP | Year | What | Python version |
|---|---|---|---|
| 484 | 2014 | Type hints | 3.5 |
| 526 | 2016 | Variable annotations | 3.6 |
| 544 | 2017 | Protocols (structural typing) | 3.8 |
| 561 | 2017 | Distributing type info (py.typed) | 3.7 |
| 585 | 2019 | Builtin generics (list[int]) | 3.9 |
| 591 | 2019 | Final qualifier | 3.8 |
| 593 | 2019 | Annotated | 3.9 |
| 604 | 2019 | X pipe Y union syntax | 3.10 |
| 612 | 2019 | ParamSpec | 3.10 |
| 646 | 2020 | TypeVarTuple | 3.11 |
| 647 | 2021 | TypeGuard | 3.10 |
| 673 | 2022 | Self type | 3.11 |
| 675 | 2022 | LiteralString | 3.11 |
| 681 | 2022 | dataclass_transform | 3.11 |
| 692 | 2023 | TypedDict for kwargs | 3.12 |
| 695 | 2022 | New generic syntax (class Box[T]:) | 3.12 |
| 696 | 2022 | TypeVar defaults | 3.13 |
| 698 | 2022 | @override decorator | 3.12 |
| 702 | 2023 | @deprecated decorator | 3.13 |
| 705 | 2023 | TypedDict readonly | 3.13 |
| 712 | 2023 | dataclass field transforms | (proposed) |
| 728 | 2023 | TypedDict closed/extra_items | (proposed) |
| 742 | 2024 | TypeIs (improved narrowing) | 3.13 |
| 749 | 2024 | Annotation evaluation spec | (in discussion) |
| 750 | 2024 | t-string (typed string literal) | (proposed) |
The headline summary.
- PEP 695 (3.12): new generic syntax. Type parameters now live next to the class:
class Box[T]:. Looks more like TypeScript. - PEP 696 (3.13): TypeVar defaults. You can write
Box[T = int]. - PEP 742 (3.13): TypeIs fixes a TypeGuard weakness — narrowing now works in both branches.
- PEP 698 (3.12): the
@overridedecorator. Verifies that a method actually overrides a parent method. - PEP 749: cleans up annotation evaluation rules. Defines what comes after
from __future__ import annotations.
Different checkers track this list at different speeds. Pyright is fastest, Mypy is slowest. ty and Pyrefly implement new PEPs from day one.
3. Mypy — the reference implementation
Mypy started in 2012, originally as Jukka Lehtosalo's PhD research. Guido van Rossum joined him at Dropbox and it took off. As of 2026 it is effectively the reference implementation of PEP 484.
# mypy example
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 error: name may be None
Running mypy on the snippet above gives you:
example.py:9: error: Item "None" of "Optional[str]" has no attribute "upper"
Found 1 error in 1 file (checked 1 source file)
Strengths.
- Reference behavior. It is the reference implementation of the typing PEPs. Other checkers are measured against mypy's behavior.
- Plugin ecosystem. Plugins exist for Django, SQLAlchemy, attrs, and other libraries with heavy dynamic behavior.
- Incremental cache. The
.mypy_cache/directory makes the second run dramatically faster. - mypyc self-compilation. Mypy itself ships compiled with mypyc, giving a noticeable speed boost.
Weaknesses.
- Speed. Cold start is slow on monorepos. Three to five minutes is common at 500k lines.
- PEP catch-up. PEP 695 took a while; PEP 742 (TypeIs) also lagged.
- Error messages. "Argument 2 to ... has incompatible type" lines tend to be long and dense.
A typical mypy.ini:
[mypy]
python_version = 3.13
strict = True
plugins = pydantic.mypy, sqlalchemy.ext.mypy.plugin
[mypy-tests.*]
disallow_untyped_defs = False
strict = True essentially turns on all hardness flags. The usual pattern: new code starts strict; legacy code gets opt-outs per module.
4. Pyright + Pylance — Microsoft, strongest IDE feedback
Pyright launched in 2019 from Microsoft. It is written in TypeScript and runs on Node.js. Inside VS Code it ships as the engine behind Pylance.
The Pyright design goal: incremental, immediate, accurate. Save a file and the result should arrive in 0.1 seconds. To pull this off Pyright does:
- AST caching. Files that did not change are not re-parsed.
- Per-symbol incremental. When one function changes, only its dependencies are rechecked.
- Aggressive inference. More aggressive than mypy in many cases (especially type narrowing).
# Pyright narrowing example
from typing import Union
def process(x: Union[int, str]) -> str:
if isinstance(x, int):
# x narrows to int here (mypy also does this)
return str(x * 2)
# x narrows to str here (mypy also does this)
return x.upper()
Basic narrowing is universal. The difference shows up in subtler cases.
# Pyright narrows here, mypy sometimes does not
from typing import Literal
def get_status() -> Literal["ok", "error"]:
...
s = get_status()
if s == "ok":
# Pyright: s is Literal["ok"]
# mypy: same in modern versions
handle_ok()
Pyright vs Pylance.
- Pyright: open source. Usable from CLI, LSP, CI.
- Pylance: VS Code only, closed source. Pyright engine plus VS Code integrations like auto-import, docstring inlay hints, and session cache.
Install Pyright via npm:
npm install -g pyright
pyright src/
Configuration lives in pyrightconfig.json or [tool.pyright] in pyproject.toml:
{
"include": ["src"],
"exclude": ["**/node_modules", "**/__pycache__"],
"typeCheckingMode": "strict",
"pythonVersion": "3.13",
"reportMissingImports": "error",
"reportUnknownArgumentType": "warning"
}
typeCheckingMode has four levels: off, basic, standard, strict. strict is usually tighter than mypy's --strict.
5. Astral's ty — Rust-based, from the ruff and uv team
Astral rewrote the Python toolchain between 2024 and 2025. Once Ruff (linter) and uv (package manager) had proven the "Rust is dramatically faster" thesis, the same team announced ty, a Rust-based type checker, in alpha in 2025.
Design principles.
- Rust native. Goal: 30 to 100 times faster cold start than mypy.
- Incremental DB. Built on Salsa, the incremental computation framework that powers rust-analyzer.
- LSP and CLI as first-class citizens. Like Pyright, IDE integration is baseline.
- Tight integration with ruff. Same workspace config and same cache directory.
As of May 2026 ty is still alpha, but new PEPs land fast. PEP 695 was supported from launch. PEP 742 and PEP 749 are already in.
# install
uv tool install ty
# check
ty check src/
# start LSP server
ty server
Configuration lives in [tool.ty] inside pyproject.toml:
[tool.ty]
python-version = "3.13"
strict = true
exclude = ["tests/"]
[tool.ty.rules]
unresolved-import = "error"
unused-ignore-comment = "warning"
The threat ty poses.
- The speed gap is huge. A 500k-line monorepo that takes 3 minutes on mypy can drop to ~5 seconds on ty even in alpha.
- For ruff and uv users, the appeal is one toolchain: same company, same cache, same config.
- But there is no plugin system yet. Dynamic libraries like SQLAlchemy 2.0 and Pydantic 2 are hard to cover with pure static analysis.
Reality check, May 2026. ty is great for new projects and small libraries. Pointing it at a 50k-line SQLAlchemy 1.x codebase still produces a lot of false positives. The consensus is it will stabilize over the next year.
6. Meta's Pyrefly — the Rust rewrite of Pyre
Meta runs a Python monorepo north of 100M lines across Instagram and Facebook backends. Pyre was Meta's OCaml-based checker built for that monorepo. As the OCaml ecosystem narrowed and the monorepo's incremental and distributed needs grew, Meta started Pyrefly between 2024 and 2025 — a Rust rewrite.
Pyrefly's strengths.
- Monorepo friendly. Distributed indexing, incremental checking, and symbol DB validated at monorepo scale.
- Type-checks at scale. Battle-tested on a 100M-line codebase inside Meta.
- OSS. Released as OSS in 2025. Usable outside Meta.
# install
cargo install pyrefly
# or
pip install pyrefly
# check
pyrefly check src/
Pyrefly config lives in pyrefly.toml or [tool.pyrefly] in pyproject.toml:
[tool.pyrefly]
python_version = "3.13"
project_root = "."
search_path = ["src", "lib"]
strict = true
Pyrefly vs ty positioning.
- Pyrefly: monorepo scale first. Validation inside Meta is the strength.
- ty: general OSS and small-to-medium projects first. The ruff and uv ecosystem fit is the strength.
Both are Rust, but they target different markets. Small library authors lean ty. Monorepo operators lean Pyrefly.
7. Google's Pytype — declining but still around
Pytype is Google's checker. It was active between 2015 and 2017 and stood out for inferring types without annotations.
# Pytype can infer types even without annotations
def add(x, y):
return x + y
# Pytype: tracks usage sites and infers int/str/float possibilities
add(1, 2) # int + int -> int
add("a", "b") # str + str -> str
Strengths.
- Strong inference on un-annotated legacy code.
- Automatic
.pyistub generation. - Long Google internal validation.
Weaknesses.
- Declining usage. Even inside Google, some teams have migrated to Pyright or Pyrefly.
- Slow PEP catch-up. New syntax like PEP 695 and PEP 742 lags.
- Speed. Some reports say it is slower than mypy on monorepos.
As of 2026, Pytype is not a great choice for new adoption. But some large projects (Apache Beam, parts of Google) still use it, so it will not disappear soon.
8. Soundness vs Speed vs Ergonomics — four-way comparison
Three axes matter when picking a checker.
- Soundness: the breadth of bugs caught. "How often does mypy let through code that breaks at runtime?"
- Speed: both cold start and incremental.
- Ergonomics: error messages, plugins, LSP, documentation.
Subjective ratings as of May 2026:
| Checker | Soundness | Cold start | Incremental | LSP | Error messages | Plugins |
|---|---|---|---|---|---|---|
| Mypy | Reference | Slow | Average | Needs daemon | Long, dense | Rich |
| Pyright | Very strong | Fast | Very fast | First-class | Short, clear | Limited |
| Pyrefly | Strong | Very fast | Very fast | First-class | Clear | In progress |
| ty | Incomplete | Very fast | Very fast | First-class | Clear | None |
Theoretical soundness is anchored by mypy. Practical soundness diverges on narrowing, overloads, and protocols.
Speed is dominated by Pyrefly and ty. A million-line codebase: 5 minutes on mypy, 30 seconds on Pyright, ~5 seconds on Pyrefly or ty.
Error messages are best in Pyright. Mypy is verbose. Pyrefly and ty are clear but still early.
Plugins are mypy's strongest moat. That said, the new tools support Pydantic 2 and SQLAlchemy 2.0 well out of the box because they were designed against modern library shapes.
9. PEP 695, TypeIs, Self, Never — what you hit in real code
Compared to PEP 484 days, 2026 Python code has noticeably different syntax. The pieces you actually meet.
PEP 695 — new generic syntax (3.12+)
Old syntax (still works):
from typing import TypeVar, Generic
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, item: T) -> None:
self.item = item
New syntax (3.12+):
class Box[T]:
def __init__(self, item: T) -> None:
self.item = item
# functions too
def first[T](items: list[T]) -> T:
return items[0]
# type aliases too
type StringList = list[str]
It looks more like TypeScript now. Pyright supported it on day one. Mypy 1.4 had partial support; 1.7 stabilized it. ty and Pyrefly have shipped it from launch.
PEP 742 — TypeIs (3.13+)
The fix for TypeGuard's narrowing weakness.
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 narrows to list[str]
print(",".join(items))
else:
# items stays list[object] — but TypeGuard would have lost info here
print("not strings")
The key difference: TypeIs narrows in both branches. The "what it is not" survives in the else branch — something TypeGuard could not do.
Self type (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 keeps the subclass type through chaining
b = FancyBuilder().with_name("alice").with_color("red")
# b: FancyBuilder, not Builder, thanks to Self
What used to require a TypeVar trick is now one word: Self.
Never type
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") # code after this is flagged as dead
Never declares "this function does not return". The checker uses it for dead-code analysis.
@override decorator (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: # typo — no such method on Base, checker flags it
return "hi"
Same role as TypeScript's override. If the parent has no method with that name, the checker errors out.
10. Strict mode and gradual adoption strategy
Strict from day one is great when you can do it. On an existing codebase you can't. The standard gradual rollout.
Step 1 — establish a baseline
First, snapshot the current state.
mypy --ignore-missing-imports src/ > baseline.txt
wc -l baseline.txt
That number is the starting point. The rule for new PRs: the number does not grow.
Step 2 — strict for new code
In mypy.ini, apply strict only to new modules.
[mypy]
python_version = 3.13
[mypy-myapp.new_module.*]
strict = True
[mypy-myapp.legacy.*]
ignore_errors = True
New code tightens from day one. Legacy is ignored for now.
Step 3 — --strict-optional first
The biggest single ROI is distinguishing Optional[X] from X. Turn this on before other strict flags.
[mypy]
strict_optional = True
no_implicit_optional = True
These two flags alone catch about 80% of None-related bugs.
Step 4 — disallow_untyped_defs
Forbid untyped function definitions.
[mypy]
disallow_untyped_defs = True
disallow_incomplete_defs = True
New functions must be typed. Existing functions get annotated gradually.
Step 5 — strict = True
All strict flags at once. The final stage.
[mypy]
strict = True
What strict = True turns on.
disallow_untyped_defsdisallow_any_genericsdisallow_untyped_callsdisallow_incomplete_defsdisallow_untyped_decoratorscheck_untyped_defsno_implicit_optionalwarn_redundant_castswarn_return_anywarn_unused_ignoresstrict_equality
11. Real setup — pre-commit, CI, pytest integration
pre-commit hooks
.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
Running both checkers is common. Mypy for depth, Pyright for speed.
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 — test the types themselves
A tool for testing whether types behave as intended.
# 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"
Drop yaml like this in and pytest runs mypy to check that reveal_type output matches expected.
inline_snapshot — snapshot tests for type output
Snapshot the output of reveal_type.
from inline_snapshot import snapshot
def test_types():
from mymodule import process
result = process(42)
assert type(result) == snapshot(int)
Type changes show up as PR diffs, intentional or not.
mypy daemon (dmypy)
The way to push incremental speed on a large codebase.
# start daemon
dmypy start -- --strict
# check (cache alive)
dmypy check src/
# status
dmypy status
# stop
dmypy stop
VS Code extensions for mypy can run dmypy automatically. Still slower than Pyright's LSP, but much faster than batch mypy.
12. Decision guide — which one for your team
By situation.
Case 1 — new OSS library.
- Main: Pyright (in CI)
- Secondary: Mypy (validate plugin compatibility at publish)
- Why: Pyright is fast and strong, but OSS consumers check with mypy, so both should pass.
Case 2 — Pydantic + FastAPI backend.
- Main: Pyright + Pylance (VS Code)
- Secondary: Mypy with pydantic.mypy plugin
- Why: Pydantic 2 fits Pyright well. Mypy needs the plugin.
Case 3 — SQLAlchemy 2.0 + Django monorepo.
- Main: Mypy with sqlalchemy.ext.mypy.plugin and django-stubs
- Secondary: Pyright (IDE only)
- Why: The SQLAlchemy 1.x to 2.0 migration leans on the mypy plugin. Pyright misses some ORM patterns.
Case 4 — new project, Astral toolchain.
- Main: ty
- Secondary: Pyright (CI hardening)
- Why: If you already use ruff and uv, ty fits naturally. Alpha, but on a green project the false-positive rate is lower.
Case 5 — 100M-line monorepo.
- Main: Pyrefly
- Secondary: none
- Why: Validated at that scale inside Meta. Other checkers OOM.
Case 6 — legacy Python 2/3 mixed code.
- Main: Pytype (short-term)
- Migrate to Mypy or Pyright afterwards
- Why: Pytype's inference of un-annotated code is strong. Not recommended for green-field adoption.
13. 2026 predictions — who survives a year from now
Closing forecast for next year.
- Pyright stays the IDE standard. Pylance's VS Code share is already overwhelming. No real challenger.
- Mypy survives as the reference. The plugin ecosystem is too big. It does not die.
- ty grows fastest. Astral's track record (ruff and uv) buys trust. Alpha to beta to stable likely within a year.
- Pyrefly settles into the monorepo niche. It is OSS, but its market does not overlap with ty.
- Pytype stays in maintenance mode. Almost no new adoption.
- Pyre effectively ends. A full migration to Pyrefly is probable within a year.
The biggest wildcard is ty's plugin system. If ty handles SQLAlchemy 2.0 and Django well out of the box, mypy's last moat falls. Or ty and mypy could share a plugin compatibility layer. Either way, the May 2027 landscape will look different again.
14. References
Official docs
- Mypy documentation
- Pyright documentation
- Pylance — VS Code Marketplace
- Pyre
- Pyrefly (Meta)
- ty (Astral)
- Pytype (Google)
Typing PEPs
- PEP 484 — Type Hints
- PEP 526 — Variable Annotations
- PEP 544 — Protocols
- PEP 561 — Distributing and Packaging Type Information
- PEP 585 — Builtin Generic Types
- PEP 604 — Union Operator
- PEP 612 — ParamSpec
- PEP 646 — TypeVarTuple
- PEP 673 — Self Type
- PEP 681 — dataclass_transform
- PEP 695 — Type Parameter Syntax
- PEP 696 — TypeVar Defaults
- PEP 698 — Override Decorator
- PEP 702 — Deprecated Decorator
- PEP 705 — TypedDict ReadOnly
- PEP 742 — TypeIs
- PEP 749 — Annotation Evaluation
Practical tools
- pytest-mypy-plugins
- pydantic.mypy plugin
- sqlalchemy.ext.mypy.plugin
- django-stubs
- typeshed (stubs for the standard library)