mypy - Static Type Checking for Python
mypy - Python静态类型检查
mypy is the standard static type checker for Python, enabling gradual typing with type hints (PEP 484) and comprehensive type safety. It catches type errors before runtime, improves code documentation, and enhances IDE support while maintaining Python's dynamic nature through incremental adoption.
Key Features:
- Gradual typing: Add types incrementally to existing code
- Strict mode: Maximum type safety with --strict flag
- Type inference: Automatically infer types from context
- Protocol support: Structural typing (duck typing with types)
- Generic types: TypeVar, Generic, and advanced type patterns
- Framework integration: FastAPI, Django, Pydantic compatibility
- Plugin system: Extend type checking for libraries
- Incremental checking: Fast type checking on large codebases
Installation:
mypy是Python的标准静态类型检查工具,通过类型提示(PEP 484)实现渐进式类型检查,提供全面的类型安全保障。它能在运行前捕获类型错误,改进代码文档,增强IDE支持,同时通过增量式采用保留Python的动态特性。
核心特性:
- 渐进式类型:为现有代码逐步添加类型提示
- 严格模式:通过--strict标志实现最高级别类型安全
- 类型推断:从上下文自动推断类型
- Protocol支持:结构化类型(带类型的鸭子类型)
- 泛型类型:TypeVar、Generic及高级类型模式
- 框架集成:兼容FastAPI、Django、Pydantic
- 插件系统:扩展库的类型检查能力
- 增量检查:对大型代码库快速进行类型检查
安装:
With common type stubs
With common type stubs
pip install mypy types-requests types-PyYAML types-redis
pip install mypy types-requests types-PyYAML types-redis
For FastAPI projects
For FastAPI projects
pip install mypy pydantic
pip install mypy pydantic
For Django projects
For Django projects
pip install mypy django-stubs
pip install mypy django-stubs
Development setup
Development setup
pip install mypy pre-commit
pip install mypy pre-commit
Type Annotation Basics
类型注解基础
1. Variable Type Hints
1. 变量类型提示
name: str = "Alice"
age: int = 30
height: float = 5.9
is_active: bool = True
name: str = "Alice"
age: int = 30
height: float = 5.9
is_active: bool = True
Type inference (mypy infers types)
Type inference (mypy infers types)
count = 10 # mypy infers: int
message = "Hello" # mypy infers: str
count = 10 # mypy infers: int
message = "Hello" # mypy infers: str
Multiple types with Union
Multiple types with Union
from typing import Union
user_id: Union[int, str] = 123 # Can be int OR str
result: Union[int, None] = None # Nullable int
from typing import Union
user_id: Union[int, str] = 123 # Can be int OR str
result: Union[int, None] = None # Nullable int
Optional (shorthand for Union[T, None])
Optional (shorthand for Union[T, None])
from typing import Optional
user_email: Optional[str] = None # Can be str or None
from typing import Optional
user_email: Optional[str] = None # Can be str or None
2. Function Type Hints
2. 函数类型提示
Basic function typing
Basic function typing
def greet(name: str) -> str:
return f"Hello, {name}"
def greet(name: str) -> str:
return f"Hello, {name}"
Multiple parameters
Multiple parameters
def add(a: int, b: int) -> int:
return a + b
def add(a: int, b: int) -> int:
return a + b
Optional parameters with defaults
Optional parameters with defaults
def create_user(name: str, age: int = 18) -> dict:
return {"name": name, "age": age}
def create_user(name: str, age: int = 18) -> dict:
return {"name": name, "age": age}
No return value
No return value
def log_message(message: str) -> None:
print(message)
def log_message(message: str) -> None:
print(message)
Functions that never return
Functions that never return
from typing import NoReturn
def raise_error() -> NoReturn:
raise ValueError("Always raises")
from typing import NoReturn
def raise_error() -> NoReturn:
raise ValueError("Always raises")
3. Collection Type Hints
3. 集合类型提示
python
from typing import List, Dict, Set, Tuple
python
from typing import List, Dict, Set, Tuple
List with element type
List with element type
numbers: List[int] = [1, 2, 3, 4]
names: List[str] = ["Alice", "Bob", "Charlie"]
numbers: List[int] = [1, 2, 3, 4]
names: List[str] = ["Alice", "Bob", "Charlie"]
Dict with key and value types
Dict with key and value types
user_ages: Dict[str, int] = {"Alice": 30, "Bob": 25}
config: Dict[str, Union[str, int]] = {"host": "localhost", "port": 8000}
user_ages: Dict[str, int] = {"Alice": 30, "Bob": 25}
config: Dict[str, Union[str, int]] = {"host": "localhost", "port": 8000}
Set with element type
Set with element type
unique_ids: Set[int] = {1, 2, 3}
unique_ids: Set[int] = {1, 2, 3}
Tuple with fixed types
Tuple with fixed types
coordinate: Tuple[float, float] = (10.5, 20.3)
user_record: Tuple[int, str, bool] = (1, "Alice", True)
coordinate: Tuple[float, float] = (10.5, 20.3)
user_record: Tuple[int, str, bool] = (1, "Alice", True)
Variable-length tuple
Variable-length tuple
numbers: Tuple[int, ...] = (1, 2, 3, 4, 5)
numbers: Tuple[int, ...] = (1, 2, 3, 4, 5)
Modern syntax (Python 3.9+)
Modern syntax (Python 3.9+)
numbers: list[int] = [1, 2, 3]
user_ages: dict[str, int] = {"Alice": 30}
numbers: list[int] = [1, 2, 3]
user_ages: dict[str, int] = {"Alice": 30}
4. Class Type Hints
4. 类类型提示
python
class User:
# Class attributes
name: str
age: int
email: Optional[str]
def __init__(self, name: str, age: int, email: Optional[str] = None) -> None:
self.name = name
self.age = age
self.email = email
def get_info(self) -> Dict[str, Union[str, int]]:
return {
"name": self.name,
"age": self.age,
"email": self.email or "N/A"
}
@classmethod
def from_dict(cls, data: Dict[str, any]) -> "User":
return cls(
name=data["name"],
age=data["age"],
email=data.get("email")
)
python
class User:
# Class attributes
name: str
age: int
email: Optional[str]
def __init__(self, name: str, age: int, email: Optional[str] = None) -> None:
self.name = name
self.age = age
self.email = email
def get_info(self) -> Dict[str, Union[str, int]]:
return {
"name": self.name,
"age": self.age,
"email": self.email or "N/A"
}
@classmethod
def from_dict(cls, data: Dict[str, any]) -> "User":
return cls(
name=data["name"],
age=data["age"],
email=data.get("email")
)
Advanced Type Hints
高级类型提示
python
from typing import Literal
python
from typing import Literal
Restrict to specific values
Restrict to specific values
def set_log_level(level: Literal["debug", "info", "warning", "error"]) -> None:
print(f"Log level: {level}")
def set_log_level(level: Literal["debug", "info", "warning", "error"]) -> None:
print(f"Log level: {level}")
set_log_level("debug")
set_log_level("error")
set_log_level("debug")
set_log_level("error")
Type error: Argument 1 has incompatible type "verbose"
Type error: Argument 1 has incompatible type "verbose"
Multiple literals
Multiple literals
Status = Literal["pending", "approved", "rejected"]
def update_status(status: Status) -> None:
pass
Status = Literal["pending", "approved", "rejected"]
def update_status(status: Status) -> None:
pass
python
from typing import Dict, List, Union
python
from typing import Dict, List, Union
UserId = int
UserName = str
def get_user(user_id: UserId) -> UserName:
return f"User {user_id}"
UserId = int
UserName = str
def get_user(user_id: UserId) -> UserName:
return f"User {user_id}"
Complex aliases
Complex aliases
JSON = Union[Dict[str, "JSON"], List["JSON"], str, int, float, bool, None]
Headers = Dict[str, str]
QueryParams = Dict[str, Union[str, int, List[str]]]
def make_request(
url: str,
headers: Headers,
params: QueryParams
) -> JSON:
pass
JSON = Union[Dict[str, "JSON"], List["JSON"], str, int, float, bool, None]
Headers = Dict[str, str]
QueryParams = Dict[str, Union[str, int, List[str]]]
def make_request(
url: str,
headers: Headers,
params: QueryParams
) -> JSON:
pass
NewType for distinct types
NewType for distinct types
from typing import NewType
UserId = NewType("UserId", int)
ProductId = NewType("ProductId", int)
def get_user(user_id: UserId) -> str:
return f"User {user_id}"
user = UserId(123) # Valid
product = ProductId(456)
get_user(user) # Valid
get_user(product) # Type error: ProductId not compatible with UserId
from typing import NewType
UserId = NewType("UserId", int)
ProductId = NewType("ProductId", int)
def get_user(user_id: UserId) -> str:
return f"User {user_id}"
user = UserId(123) # Valid
product = ProductId(456)
get_user(user) # Valid
get_user(product) # Type error: ProductId not compatible with UserId
3. Generics and TypeVar
3. 泛型与TypeVar
python
from typing import TypeVar, Generic, List
python
from typing import TypeVar, Generic, List
TypeVar for generic functions
TypeVar for generic functions
T = TypeVar("T")
def first_element(items: List[T]) -> T:
return items[0]
T = TypeVar("T")
def first_element(items: List[T]) -> T:
return items[0]
Type inference
Type inference
num = first_element([1, 2, 3]) # mypy infers: int
name = first_element(["Alice", "Bob"]) # mypy infers: str
num = first_element([1, 2, 3]) # mypy infers: int
name = first_element(["Alice", "Bob"]) # mypy infers: str
Bounded TypeVar
Bounded TypeVar
from typing import Union
NumericType = TypeVar("NumericType", int, float)
def add_numbers(a: NumericType, b: NumericType) -> NumericType:
return a + b
from typing import Union
NumericType = TypeVar("NumericType", int, float)
def add_numbers(a: NumericType, b: NumericType) -> NumericType:
return a + b
Generic classes
Generic classes
class Stack(Generic[T]):
def init(self) -> None:
self._items: List[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
def is_empty(self) -> bool:
return len(self._items) == 0
class Stack(Generic[T]):
def init(self) -> None:
self._items: List[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
def is_empty(self) -> bool:
return len(self._items) == 0
Usage with type inference
Usage with type inference
int_stack: Stack[int] = Stack()
int_stack.push(42)
int_stack.push("hello") # Type error: Expected int, got str
str_stack: Stack[str] = Stack()
str_stack.push("hello") # Valid
int_stack: Stack[int] = Stack()
int_stack.push(42)
int_stack.push("hello") # Type error: Expected int, got str
str_stack: Stack[str] = Stack()
str_stack.push("hello") # Valid
4. Protocol (Structural Typing)
4. Protocol(结构化类型)
python
from typing import Protocol
python
from typing import Protocol
Define protocol (interface)
Define protocol (interface)
class Drawable(Protocol):
def draw(self) -> str:
...
class Drawable(Protocol):
def draw(self) -> str:
...
Any class with draw() method matches
Any class with draw() method matches
class Circle:
def draw(self) -> str:
return "Drawing circle"
class Square:
def draw(self) -> str:
return "Drawing square"
class Circle:
def draw(self) -> str:
return "Drawing circle"
class Square:
def draw(self) -> str:
return "Drawing square"
Function accepts any Drawable
Function accepts any Drawable
def render(obj: Drawable) -> str:
return obj.draw()
def render(obj: Drawable) -> str:
return obj.draw()
Both work (duck typing with types)
Both work (duck typing with types)
circle = Circle()
square = Square()
render(circle) # Valid
render(square) # Valid
circle = Circle()
square = Square()
render(circle) # Valid
render(square) # Valid
Runtime checkable protocols
Runtime checkable protocols
from typing import runtime_checkable
@runtime_checkable
class Closeable(Protocol):
def close(self) -> None:
...
class File:
def close(self) -> None:
pass
from typing import runtime_checkable
@runtime_checkable
class Closeable(Protocol):
def close(self) -> None:
...
class File:
def close(self) -> None:
pass
Runtime check
Runtime check
f = File()
isinstance(f, Closeable) # True
f = File()
isinstance(f, Closeable) # True
5. Callable Types
5. 可调用类型
python
from typing import Callable
python
from typing import Callable
Function that takes another function
Function that takes another function
def apply_twice(func: Callable[[int], int], value: int) -> int:
return func(func(value))
def double(x: int) -> int:
return x * 2
result = apply_twice(double, 5) # Returns 20
def apply_twice(func: Callable[[int], int], value: int) -> int:
return func(func(value))
def double(x: int) -> int:
return x * 2
result = apply_twice(double, 5) # Returns 20
Generic callable
Generic callable
from typing import TypeVar
T = TypeVar("T")
R = TypeVar("R")
def map_values(
func: Callable[[T], R],
values: List[T]
) -> List[R]:
return [func(v) for v in values]
from typing import TypeVar
T = TypeVar("T")
R = TypeVar("R")
def map_values(
func: Callable[[T], R],
values: List[T]
) -> List[R]:
return [func(v) for v in values]
Callable with multiple arguments
Callable with multiple arguments
Validator = Callable[[str, int], bool]
def validate_user(name: str, age: int) -> bool:
return len(name) > 0 and age >= 0
validator: Validator = validate_user
Validator = Callable[[str, int], bool]
def validate_user(name: str, age: int) -> bool:
return len(name) > 0 and age >= 0
validator: Validator = validate_user
1. mypy.ini Configuration
1. mypy.ini配置
Python version
Python version
Import discovery
Import discovery
files = src,tests
exclude = build,dist,venv
files = src,tests
exclude = build,dist,venv
Type checking strictness
Type checking strictness
disallow_untyped_defs = True
disallow_any_unimported = False
no_implicit_optional = True
warn_return_any = True
warn_unused_ignores = True
warn_redundant_casts = True
disallow_untyped_defs = True
disallow_any_unimported = False
no_implicit_optional = True
warn_return_any = True
warn_unused_ignores = True
warn_redundant_casts = True
Error reporting
Error reporting
show_error_codes = True
show_column_numbers = True
pretty = True
show_error_codes = True
show_column_numbers = True
pretty = True
Incremental type checking
Incremental type checking
incremental = True
cache_dir = .mypy_cache
incremental = True
cache_dir = .mypy_cache
Per-module configuration
Per-module configuration
[mypy-tests.*]
disallow_untyped_defs = False
[mypy-migrations.*]
ignore_errors = True
[mypy-tests.*]
disallow_untyped_defs = False
[mypy-migrations.*]
ignore_errors = True
Third-party libraries without stubs
Third-party libraries without stubs
[mypy-redis.*]
ignore_missing_imports = True
[mypy-celery.*]
ignore_missing_imports = True
[mypy-redis.*]
ignore_missing_imports = True
[mypy-celery.*]
ignore_missing_imports = True
2. pyproject.toml Configuration
2. pyproject.toml配置
pyproject.toml
pyproject.toml
[tool.mypy]
python_version = "3.11"
files = ["src", "tests"]
exclude = ["build", "dist", "venv"]
[tool.mypy]
python_version = "3.11"
files = ["src", "tests"]
exclude = ["build", "dist", "venv"]
disallow_untyped_defs = true
disallow_any_unimported = false
no_implicit_optional = true
warn_return_any = true
warn_unused_ignores = true
warn_redundant_casts = true
strict_equality = true
strict_concatenate = true
disallow_untyped_defs = true
disallow_any_unimported = false
no_implicit_optional = true
warn_return_any = true
warn_unused_ignores = true
warn_redundant_casts = true
strict_equality = true
strict_concatenate = true
Error reporting
Error reporting
show_error_codes = true
show_column_numbers = true
pretty = true
color_output = true
show_error_codes = true
show_column_numbers = true
pretty = true
color_output = true
incremental = true
cache_dir = ".mypy_cache"
incremental = true
cache_dir = ".mypy_cache"
Per-module overrides
Per-module overrides
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
[[tool.mypy.overrides]]
module = ["redis.", "celery."]
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
[[tool.mypy.overrides]]
module = ["redis.", "celery."]
ignore_missing_imports = true
Enable all strict checks
Enable all strict checks
Strict mode equivalent flags
Strict mode equivalent flags
mypy
--disallow-any-unimported
--disallow-any-expr
--disallow-any-decorated
--disallow-any-explicit
--disallow-any-generics
--disallow-subclassing-any
--disallow-untyped-calls
--disallow-untyped-defs
--disallow-incomplete-defs
--check-untyped-defs
--disallow-untyped-decorators
--no-implicit-optional
--warn-redundant-casts
--warn-unused-ignores
--warn-return-any
--warn-unreachable
--strict-equality
src/
mypy
--disallow-any-unimported
--disallow-any-expr
--disallow-any-decorated
--disallow-any-explicit
--disallow-any-generics
--disallow-subclassing-any
--disallow-untyped-calls
--disallow-untyped-defs
--disallow-incomplete-defs
--check-untyped-defs
--disallow-untyped-decorators
--no-implicit-optional
--warn-redundant-casts
--warn-unused-ignores
--warn-return-any
--warn-unreachable
--strict-equality
src/
mypy.ini strict configuration
mypy.ini strict configuration
Relax specific checks if needed
Relax specific checks if needed
disallow_any_expr = False # Too strict for most projects
disallow_any_explicit = False # Allow explicit Any
disallow_any_expr = False # Too strict for most projects
disallow_any_explicit = False # Allow explicit Any
Incremental Adoption Strategies
增量式采用策略
1. Start with Entry Points
1. 从入口文件开始
Start typing from main.py (top-level)
Start typing from main.py (top-level)
from typing import Optional
from app.services import UserService
def main(config_path: Optional[str] = None) -> None:
"""Application entry point."""
service = UserService()
service.run()
if name == "main":
main()
from typing import Optional
from app.services import UserService
def main(config_path: Optional[str] = None) -> None:
"""Application entry point."""
service = UserService()
service.run()
if name == "main":
main()
Check only main.py initially
Check only main.py initially
Gradually expand scope
Gradually expand scope
mypy main.py app/services.py
mypy src/
mypy main.py app/services.py
mypy src/
2. Per-Module Strict Mode
2. 按模块启用严格模式
mypy.ini - Gradually enable strict checking
mypy.ini - Gradually enable strict checking
Lenient global defaults
Lenient global defaults
ignore_missing_imports = True
disallow_untyped_defs = False
ignore_missing_imports = True
disallow_untyped_defs = False
Strict for new modules
Strict for new modules
[mypy-app.services.user_service]
disallow_untyped_defs = True
warn_return_any = True
[mypy-app.api.*]
disallow_untyped_defs = True
no_implicit_optional = True
[mypy-app.services.user_service]
disallow_untyped_defs = True
warn_return_any = True
[mypy-app.api.*]
disallow_untyped_defs = True
no_implicit_optional = True
Still lenient for legacy code
Still lenient for legacy code
[mypy-app.legacy.*]
ignore_errors = True
[mypy-app.legacy.*]
ignore_errors = True
3. Use # type: ignore Strategically
3. 策略性使用# type: ignore
Suppress specific errors during migration
Suppress specific errors during migration
import legacy_module # type: ignore[import]
def process_data(data): # type: ignore[no-untyped-def]
# TODO: Add type hints
return data.transform()
import legacy_module # type: ignore[import]
def process_data(data): # type: ignore[no-untyped-def]
# TODO: Add type hints
return data.transform()
Ignore specific error codes
Ignore specific error codes
user_dict = get_user_dict()
user_id = user_dict["id"] # type: ignore[index]
user_dict = get_user_dict()
user_id = user_dict["id"] # type: ignore[index]
Ignore entire line (use sparingly)
Ignore entire line (use sparingly)
result = external_api.call() # type: ignore
result = external_api.call() # type: ignore
4. Reveal Types During Development
4. 开发期间使用reveal_type
python
from typing import reveal_type
def process_user(user_id: int):
user = get_user(user_id)
reveal_type(user) # mypy will show inferred type
name = user.name
reveal_type(name) # mypy will show: str
python
from typing import reveal_type
def process_user(user_id: int):
user = get_user(user_id)
reveal_type(user) # mypy will show inferred type
name = user.name
reveal_type(name) # mypy will show: str
FastAPI Integration
FastAPI集成
1. FastAPI with Type Hints
1. 带类型提示的FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI()
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI()
Pydantic models (auto-validated)
Pydantic models (auto-validated)
class User(BaseModel):
id: int
name: str
email: str
age: Optional[int] = None
class UserCreate(BaseModel):
name: str
email: str
age: Optional[int] = None
class User(BaseModel):
id: int
name: str
email: str
age: Optional[int] = None
class UserCreate(BaseModel):
name: str
email: str
age: Optional[int] = None
Type-safe endpoints
Type-safe endpoints
@app.get("/")
def read_root() -> dict[str, str]:
return {"message": "Hello World"}
@app.get("/users/{user_id}")
def read_user(user_id: int) -> User:
if user_id == 0:
raise HTTPException(status_code=404, detail="User not found")
return User(id=user_id, name=f"User {user_id}", email="user@example.com")
@app.get("/users")
def list_users(skip: int = 0, limit: int = 10) -> List[User]:
users = [
User(id=i, name=f"User {i}", email=f"user{i}@example.com")
for i in range(skip, skip + limit)
]
return users
@app.post("/users")
def create_user(user: UserCreate) -> User:
# Pydantic ensures type safety
return User(id=1, name=user.name, email=user.email, age=user.age)
@app.get("/")
def read_root() -> dict[str, str]:
return {"message": "Hello World"}
@app.get("/users/{user_id}")
def read_user(user_id: int) -> User:
if user_id == 0:
raise HTTPException(status_code=404, detail="User not found")
return User(id=user_id, name=f"User {user_id}", email="user@example.com")
@app.get("/users")
def list_users(skip: int = 0, limit: int = 10) -> List[User]:
users = [
User(id=i, name=f"User {i}", email=f"user{i}@example.com")
for i in range(skip, skip + limit)
]
return users
@app.post("/users")
def create_user(user: UserCreate) -> User:
# Pydantic ensures type safety
return User(id=1, name=user.name, email=user.email, age=user.age)
2. Async FastAPI Type Checking
2. 异步FastAPI类型检查
python
from typing import List, Optional
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import AsyncSession
app = FastAPI()
python
from typing import List, Optional
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import AsyncSession
app = FastAPI()
Async dependency
Async dependency
async def get_db() -> AsyncSession:
async with AsyncSessionLocal() as session:
yield session
async def get_db() -> AsyncSession:
async with AsyncSessionLocal() as session:
yield session
Async endpoints with types
Async endpoints with types
@app.get("/users/{user_id}")
async def read_user(
user_id: int,
db: AsyncSession = Depends(get_db)
) -> User:
user = await db.get(User, user_id)
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user
@app.get("/users")
async def list_users(
skip: int = 0,
limit: int = 10,
db: AsyncSession = Depends(get_db)
) -> List[User]:
result = await db.execute(
select(User).offset(skip).limit(limit)
)
return result.scalars().all()
@app.get("/users/{user_id}")
async def read_user(
user_id: int,
db: AsyncSession = Depends(get_db)
) -> User:
user = await db.get(User, user_id)
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user
@app.get("/users")
async def list_users(
skip: int = 0,
limit: int = 10,
db: AsyncSession = Depends(get_db)
) -> List[User]:
result = await db.execute(
select(User).offset(skip).limit(limit)
)
return result.scalars().all()
3. FastAPI Dependency Injection Types
3. FastAPI依赖注入类型
python
from typing import Annotated, Optional
from fastapi import FastAPI, Depends, Header, HTTPException
app = FastAPI()
python
from typing import Annotated, Optional
from fastapi import FastAPI, Depends, Header, HTTPException
app = FastAPI()
Typed dependencies
Typed dependencies
async def get_current_user(
authorization: Annotated[Optional[str], Header()] = None
) -> User:
if authorization is None:
raise HTTPException(status_code=401, detail="Not authenticated")
# Verify token and return user
return User(id=1, name="Current User", email="user@example.com")
async def get_current_user(
authorization: Annotated[Optional[str], Header()] = None
) -> User:
if authorization is None:
raise HTTPException(status_code=401, detail="Not authenticated")
# Verify token and return user
return User(id=1, name="Current User", email="user@example.com")
Use dependency with type annotation
Use dependency with type annotation
@app.get("/me")
async def read_current_user(
current_user: Annotated[User, Depends(get_current_user)]
) -> User:
return current_user
@app.get("/me")
async def read_current_user(
current_user: Annotated[User, Depends(get_current_user)]
) -> User:
return current_user
Complex dependency chain
Complex dependency chain
class UserService:
def init(self, db: AsyncSession) -> None:
self.db = db
async def get_user(self, user_id: int) -> Optional[User]:
return await self.db.get(User, user_id)
def get_user_service(
db: Annotated[AsyncSession, Depends(get_db)]
) -> UserService:
return UserService(db)
@app.get("/users/{user_id}")
async def get_user_endpoint(
user_id: int,
service: Annotated[UserService, Depends(get_user_service)]
) -> User:
user = await service.get_user(user_id)
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user
class UserService:
def init(self, db: AsyncSession) -> None:
self.db = db
async def get_user(self, user_id: int) -> Optional[User]:
return await self.db.get(User, user_id)
def get_user_service(
db: Annotated[AsyncSession, Depends(get_db)]
) -> UserService:
return UserService(db)
@app.get("/users/{user_id}")
async def get_user_endpoint(
user_id: int,
service: Annotated[UserService, Depends(get_user_service)]
) -> User:
user = await service.get_user(user_id)
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user
Django Integration
Django集成
1. Django with django-stubs
1. Django与django-stubs
Install django-stubs
Install django-stubs
pip install django-stubs mypy
pip install django-stubs mypy
Generate mypy configuration
Generate mypy configuration
python -m mypy --install-types
python -m mypy --install-types
[mypy]
plugins = mypy_django_plugin.main
[mypy.plugins.django-stubs]
django_settings_module = "myproject.settings"
[mypy]
plugins = mypy_django_plugin.main
[mypy.plugins.django-stubs]
django_settings_module = "myproject.settings"
2. Django Models with Type Hints
2. 带类型提示的Django模型
from django.db import models
from typing import Optional
class User(models.Model):
email: models.EmailField = models.EmailField(unique=True)
name: models.CharField = models.CharField(max_length=100)
age: models.IntegerField = models.IntegerField(null=True, blank=True)
is_active: models.BooleanField = models.BooleanField(default=True)
created_at: models.DateTimeField = models.DateTimeField(auto_now_add=True)
def get_display_name(self) -> str:
return self.name or self.email
@classmethod
def get_active_users(cls) -> models.QuerySet["User"]:
return cls.objects.filter(is_active=True)
from django.db import models
from typing import Optional
class User(models.Model):
email: models.EmailField = models.EmailField(unique=True)
name: models.CharField = models.CharField(max_length=100)
age: models.IntegerField = models.IntegerField(null=True, blank=True)
is_active: models.BooleanField = models.BooleanField(default=True)
created_at: models.DateTimeField = models.DateTimeField(auto_now_add=True)
def get_display_name(self) -> str:
return self.name or self.email
@classmethod
def get_active_users(cls) -> models.QuerySet["User"]:
return cls.objects.filter(is_active=True)
3. Django Views with Type Hints
3. 带类型提示的Django视图
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from typing import Dict, Any
from .models import User
def user_detail(request: HttpRequest, user_id: int) -> JsonResponse:
user: User = get_object_or_404(User, pk=user_id)
data: Dict[str, Any] = {
"id": user.id,
"name": user.name,
"email": user.email,
}
return JsonResponse(data)
def user_list(request: HttpRequest) -> JsonResponse:
users = User.get_active_users()
data = {
"users": list(users.values("id", "name", "email"))
}
return JsonResponse(data)
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from typing import Dict, Any
from .models import User
def user_detail(request: HttpRequest, user_id: int) -> JsonResponse:
user: User = get_object_or_404(User, pk=user_id)
data: Dict[str, Any] = {
"id": user.id,
"name": user.name,
"email": user.email,
}
return JsonResponse(data)
def user_list(request: HttpRequest) -> JsonResponse:
users = User.get_active_users()
data = {
"users": list(users.values("id", "name", "email"))
}
return JsonResponse(data)
Type Stubs and Third-Party Libraries
类型存根与第三方库
1. Installing Type Stubs
1. 安装类型存根
Install stubs for popular libraries
Install stubs for popular libraries
pip install types-requests
pip install types-PyYAML
pip install types-redis
pip install types-boto3
pip install types-requests
pip install types-PyYAML
pip install types-redis
pip install types-boto3
Search for available stubs
Search for available stubs
Auto-install missing stubs
Auto-install missing stubs
2. Creating Custom Stubs
2. 创建自定义类型存根
stubs/external_lib.pyi
stubs/external_lib.pyi
from typing import Optional, List
class Client:
def init(self, api_key: str) -> None: ...
def get_user(self, user_id: int) -> Optional[dict]: ...
def list_users(self, limit: int = 10) -> List[dict]: ...
def connect(host: str, port: int) -> Client: ...
from typing import Optional, List
class Client:
def init(self, api_key: str) -> None: ...
def get_user(self, user_id: int) -> Optional[dict]: ...
def list_users(self, limit: int = 10) -> List[dict]: ...
def connect(host: str, port: int) -> Client: ...
3. Ignoring Missing Imports
3. 忽略缺失的导入
[mypy-external_lib.*]
ignore_missing_imports = True
[mypy-external_lib.*]
ignore_missing_imports = True
For multiple libraries
For multiple libraries
[mypy-celery.,redis.,boto3.*]
ignore_missing_imports = True
[mypy-celery.,redis.,boto3.*]
ignore_missing_imports = True
1. GitHub Actions
1. GitHub Actions
.github/workflows/type-check.yml
.github/workflows/type-check.yml
name: Type Check
on: [push, pull_request]
jobs:
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install mypy
pip install -r requirements.txt
pip install types-requests types-PyYAML
- name: Run mypy
run: mypy src/
- name: Run mypy strict on new code
run: mypy --strict src/api/
name: Type Check
on: [push, pull_request]
jobs:
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install mypy
pip install -r requirements.txt
pip install types-requests types-PyYAML
- name: Run mypy
run: mypy src/
- name: Run mypy strict on new code
run: mypy --strict src/api/
2. Pre-commit Hook
2. Pre-commit钩子
.pre-commit-config.yaml
.pre-commit-config.yaml
Install pre-commit
Install pre-commit
pip install pre-commit
pre-commit install
pip install pre-commit
pre-commit install
pre-commit run mypy --all-files
pre-commit run mypy --all-files
.PHONY: typecheck
typecheck:
mypy src/
.PHONY: typecheck-strict
typecheck-strict:
mypy --strict src/
.PHONY: typecheck-report
typecheck-report:
mypy src/ --html-report mypy-report
@echo "Report: mypy-report/index.html"
.PHONY: ci
ci: typecheck test lint
.PHONY: typecheck
typecheck:
mypy src/
.PHONY: typecheck-strict
typecheck-strict:
mypy --strict src/
.PHONY: typecheck-report
typecheck-report:
mypy src/ --html-report mypy-report
@echo "Report: mypy-report/index.html"
.PHONY: ci
ci: typecheck test lint
Common Patterns and Idioms
常见模式与惯用写法
1. Overload for Multiple Signatures
1. 为多个签名使用Overload
python
from typing import overload, Union
@overload
def process(data: str) -> str: ...
@overload
def process(data: int) -> int: ...
@overload
def process(data: list) -> list: ...
def process(data: Union[str, int, list]) -> Union[str, int, list]:
"""Process different data types."""
if isinstance(data, str):
return data.upper()
elif isinstance(data, int):
return data * 2
else:
return [x * 2 for x in data]
python
from typing import overload, Union
@overload
def process(data: str) -> str: ...
@overload
def process(data: int) -> int: ...
@overload
def process(data: list) -> list: ...
def process(data: Union[str, int, list]) -> Union[str, int, list]:
"""Process different data types."""
if isinstance(data, str):
return data.upper()
elif isinstance(data, int):
return data * 2
else:
return [x * 2 for x in data]
mypy knows return types
mypy knows return types
result1: str = process("hello") # Valid
result2: int = process(42) # Valid
result3: str = process(42) # Type error
result1: str = process("hello") # Valid
result2: int = process(42) # Valid
result3: str = process(42) # Type error
2. TypedDict for Structured Dicts
2. 为结构化字典使用TypedDict
python
from typing import TypedDict, Optional
class UserDict(TypedDict):
id: int
name: str
email: str
age: Optional[int]
python
from typing import TypedDict, Optional
class UserDict(TypedDict):
id: int
name: str
email: str
age: Optional[int]
Type-safe dict usage
Type-safe dict usage
def create_user(data: UserDict) -> UserDict:
return {
"id": 1,
"name": data["name"],
"email": data["email"],
"age": data.get("age"),
}
user: UserDict = {
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"age": 30
}
def create_user(data: UserDict) -> UserDict:
return {
"id": 1,
"name": data["name"],
"email": data["email"],
"age": data.get("age"),
}
user: UserDict = {
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"age": 30
}
Type error: Missing required key "email"
Type error: Missing required key "email"
invalid_user: UserDict = {
"id": 1,
"name": "Bob",
}
invalid_user: UserDict = {
"id": 1,
"name": "Bob",
}
3. Final and Constant Values
3. Final与常量值
python
from typing import Final
python
from typing import Final
Constants that should never change
Constants that should never change
API_VERSION: Final = "v1"
MAX_RETRIES: Final[int] = 3
API_VERSION: Final = "v1"
MAX_RETRIES: Final[int] = 3
Type error: Cannot assign to final name
Type error: Cannot assign to final name
Final class (cannot be subclassed)
Final class (cannot be subclassed)
from typing import final
@final
class BaseConfig:
pass
from typing import final
@final
class BaseConfig:
pass
Type error: Cannot inherit from final class
Type error: Cannot inherit from final class
class AppConfig(BaseConfig): # Error!
pass
class AppConfig(BaseConfig): # Error!
pass
4. Self Type for Method Chaining
4. 方法链式调用的Self类型
python
from typing import Self # Python 3.11+
class Builder:
def __init__(self) -> None:
self._value = 0
def add(self, value: int) -> Self:
self._value += value
return self
def multiply(self, value: int) -> Self:
self._value *= value
return self
def build(self) -> int:
return self._value
python
from typing import Self # Python 3.11+
class Builder:
def __init__(self) -> None:
self._value = 0
def add(self, value: int) -> Self:
self._value += value
return self
def multiply(self, value: int) -> Self:
self._value *= value
return self
def build(self) -> int:
return self._value
Type-safe method chaining
Type-safe method chaining
result = Builder().add(5).multiply(2).add(3).build()
result = Builder().add(5).multiply(2).add(3).build()
mypy vs pyright Comparison
mypy与pyright对比
| Feature | mypy | pyright |
|---|
| Type Checker | Official Python type checker | Microsoft's type checker |
| Speed | Slower on large codebases | Faster, incremental |
| Strictness | Configurable strict mode | Very strict by default |
| IDE Integration | Good (LSP support) | Excellent (Pylance in VS Code) |
| Plugin System | Yes (mypy plugins) | Limited |
| Error Messages | Clear, detailed | Very detailed, helpful |
| Community | Large, official | Growing, Microsoft-backed |
| Type Inference | Good | Excellent |
| Configuration | mypy.ini, pyproject.toml | pyrightconfig.json, pyproject.toml |
| 特性 | mypy | pyright |
|---|
| 类型检查器 | 官方Python类型检查器 | Microsoft开发的类型检查器 |
| 速度 | 大型代码库上速度较慢 | 更快,支持增量检查 |
| 严格性 | 可配置严格模式 | 默认非常严格 |
| IDE集成 | 良好(支持LSP) | 优秀(VS Code中的Pylance) |
| 插件系统 | 支持(mypy插件) | 有限 |
| 错误信息 | 清晰、详细 | 非常详细、有帮助 |
| 社区 | 庞大、官方支持 | 正在增长,由Microsoft支持 |
| 类型推断 | 良好 | 优秀 |
| 配置 | mypy.ini、pyproject.toml | pyrightconfig.json、pyproject.toml |
Use mypy for:
Use mypy for:
- Official Python type checking standard
- 官方Python类型检查标准
- Plugin ecosystem (Django, SQLAlchemy, Pydantic)
- 插件生态系统(Django、SQLAlchemy、Pydantic)
- Gradual typing with fine-grained control
- 细粒度控制的渐进式类型检查
- Compatibility with existing mypy configurations
- 与现有mypy配置兼容
- CI/CD pipelines (industry standard)
- CI/CD流水线(行业标准)
When to Use pyright
何时使用pyright
Use pyright for:
Use pyright for:
- VS Code development (Pylance)
- VS Code开发(Pylance)
- Faster type checking on large codebases
- 大型代码库上更快的类型检查
- Stricter type checking by default
- 默认更严格的类型检查
- Better type inference
- 更优秀的类型推断
- Real-time IDE feedback
- 实时IDE反馈
pyproject.toml - Configure both
pyproject.toml - Configure both
[tool.mypy]
strict = true
files = ["src"]
[tool.pyright]
include = ["src"]
strict = ["src/api"]
reportMissingTypeStubs = false
[tool.mypy]
strict = true
files = ["src"]
[tool.pyright]
include = ["src"]
strict = ["src/api"]
reportMissingTypeStubs = false
Run both in CI
Run both in CI
Local mypy Profiles (Your Repos)
本地mypy配置文件(你的仓库)
Common patterns from your Python projects:
- Strict default (edgar, kuzu-memory, mcp-browser):
disallow_untyped_defs = true
, check_untyped_defs = true
, no_implicit_optional = true
, , .
- Relaxed profile (mcp-ticketer): strict flags disabled temporarily with a list for patch releases.
- Incremental adoption (mcp-vector-search): while stabilizing types.
- Missing imports:
ignore_missing_imports = true
used in mcp-memory and mcp-ticketer.
Reference: see
in
,
,
, and
.
你的Python项目中的常见模式:
- 严格默认配置(edgar、kuzu-memory、mcp-browser):
disallow_untyped_defs = true
, check_untyped_defs = true
, no_implicit_optional = true
, , 。
- 宽松配置(mcp-ticketer):临时禁用严格标志,为补丁版本添加列表。
- 增量式采用(mcp-vector-search):稳定类型期间设置。
- 缺失导入处理:mcp-memory和mcp-ticketer中使用
ignore_missing_imports = true
。
1. Start with Key Modules
1. 从核心模块开始
✅ GOOD: Type critical business logic first
✅ 推荐:优先为关键业务逻辑添加类型
services/user_service.py
services/user_service.py
from typing import Optional
class UserService:
def get_user(self, user_id: int) -> Optional[User]:
"""Retrieve user by ID."""
return self.db.query(User).get(user_id)
def create_user(self, data: UserCreate) -> User:
"""Create new user."""
user = User(**data.dict())
self.db.add(user)
self.db.commit()
return user
from typing import Optional
class UserService:
def get_user(self, user_id: int) -> Optional[User]:
"""根据ID检索用户。"""
return self.db.query(User).get(user_id)
def create_user(self, data: UserCreate) -> User:
"""创建新用户。"""
user = User(**data.dict())
self.db.add(user)
self.db.commit()
return user
2. Use Type Aliases for Readability
2. 使用类型别名提升可读性
✅ GOOD: Clear, reusable type aliases
✅ 推荐:清晰、可复用的类型别名
from typing import Dict, List, Union
JSON = Union[Dict[str, "JSON"], List["JSON"], str, int, float, bool, None]
Headers = Dict[str, str]
UserId = int
def parse_response(data: JSON, headers: Headers) -> UserId:
pass
from typing import Dict, List, Union
JSON = Union[Dict[str, "JSON"], List["JSON"], str, int, float, bool, None]
Headers = Dict[str, str]
UserId = int
def parse_response(data: JSON, headers: Headers) -> UserId:
pass
❌ BAD: Complex inline types
❌ 不推荐:复杂的内联类型
def parse_response(
data: Union[Dict[str, Union[...]], List[...], str, int, float, bool, None],
headers: Dict[str, str]
) -> int:
pass
def parse_response(
data: Union[Dict[str, Union[...]], List[...], str, int, float, bool, None],
headers: Dict[str, str]
) -> int:
pass
3. Prefer Explicit Over Implicit
3. 优先显式类型而非隐式推断
✅ GOOD: Explicit types for public APIs
✅ 推荐:公共API使用显式类型
def get_user(user_id: int) -> Optional[User]:
return db.query(User).get(user_id)
def get_user(user_id: int) -> Optional[User]:
return db.query(User).get(user_id)
❌ ACCEPTABLE: Type inference for internal helpers
❌ 可接受:内部辅助函数使用类型推断
def _format_name(first, last): # mypy infers str -> str
return f"{first} {last}"
def _format_name(first, last): # mypy infers str -> str
return f"{first} {last}"
4. Use reveal_type for Debugging
4. 使用reveal_type调试
During development, check inferred types
开发期间,检查推断的类型
from typing import reveal_type
def process_data(data):
result = transform(data)
reveal_type(result) # mypy: Revealed type is "int"
return result * 2
from typing import reveal_type
def process_data(data):
result = transform(data)
reveal_type(result) # mypy: Revealed type is "int"
return result * 2
5. Document Type Ignores
5. 为类型忽略添加文档说明
✅ GOOD: Document why type checking is disabled
✅ 推荐:说明禁用类型检查的原因
import legacy_module # type: ignore[import] # TODO: Add type stubs
import legacy_module # type: ignore[import] # TODO: 添加类型存根
❌ BAD: No explanation
❌ 不推荐:无任何说明
import legacy_module # type: ignore
import legacy_module # type: ignore
❌ Anti-Pattern 1: Using Any Everywhere
❌ 反模式1:到处使用Any
WRONG: Defeats purpose of type checking
错误:失去类型检查的意义
from typing import Any
def process(data: Any) -> Any:
return data.transform()
**Correct:**
```python
from typing import Union, Protocol
class Transformable(Protocol):
def transform(self) -> dict: ...
def process(data: Transformable) -> dict:
return data.transform()
from typing import Any
def process(data: Any) -> Any:
return data.transform()
**正确写法:**
```python
from typing import Union, Protocol
class Transformable(Protocol):
def transform(self) -> dict: ...
def process(data: Transformable) -> dict:
return data.transform()
❌ Anti-Pattern 2: Ignoring Type Errors Globally
❌ 反模式2:全局忽略类型错误
WRONG: Disables type checking
错误:完全禁用类型检查
[mypy]
ignore_errors = True
[mypy]
ignore_errors = True
Ignore specific modules only
仅忽略特定模块
[mypy-legacy.*]
ignore_errors = True
[mypy]
strict = True
[mypy-legacy.*]
ignore_errors = True
[mypy]
strict = True
❌ Anti-Pattern 3: Not Using Optional
❌ 反模式3:不使用Optional
WRONG: Nullable without Optional
错误:可空值未标记Optional
def get_user(user_id: int) -> User:
user = db.get(user_id) # Can be None!
return user # Runtime error if None
**Correct:**
```python
from typing import Optional
def get_user(user_id: int) -> Optional[User]:
return db.get(user_id)
def get_user(user_id: int) -> User:
user = db.get(user_id) # 可能为None!
return user # 为None时会触发运行时错误
**正确写法:**
```python
from typing import Optional
def get_user(user_id: int) -> Optional[User]:
return db.get(user_id)
Handle None explicitly
显式处理None情况
user = get_user(123)
if user is not None:
print(user.name)
user = get_user(123)
if user is not None:
print(user.name)
Basic type checking
基础类型检查
Install missing type stubs
安装缺失的类型存根
Generate HTML report
生成HTML报告
mypy src/ --html-report mypy-report
mypy src/ --html-report mypy-report
Check specific error codes
显示错误代码
mypy --show-error-codes src/
mypy --show-error-codes src/
Ignore missing imports
忽略缺失的导入
mypy --ignore-missing-imports src/
mypy --ignore-missing-imports src/
mypy --follow-imports=silent src/
mypy --follow-imports=silent src/
Incremental mode (faster)
增量模式(更快)
Error Code Reference
错误代码参考
[attr-defined] # Attribute not defined
[arg-type] # Argument type mismatch
[return-value] # Return type mismatch
[assignment] # Assignment type mismatch
[call-overload] # No matching overload
[index] # Invalid index operation
[operator] # Unsupported operand type
[import] # Cannot find import
[misc] # Miscellaneous type error
[no-untyped-def] # Function missing type annotation
[var-annotated] # Variable needs type annotation
[attr-defined] # 属性未定义
[arg-type] # 参数类型不匹配
[return-value] # 返回类型不匹配
[assignment] # 赋值类型不匹配
[call-overload] # 无匹配的重载
[index] # 无效的索引操作
[operator] # 不支持的操作数类型
[import] # 无法找到导入
[misc] # 其他类型错误
[no-untyped-def] # 函数缺少类型注解
[var-annotated] # 变量需要类型注解
When using mypy, consider these complementary skills (available in the skill library):
- pytest: Type-safe testing with mypy - integrates type checking into your test suite for comprehensive type coverage
- fastapi-local-dev: FastAPI with full type safety - combines FastAPI's runtime validation with mypy's static checking
- pydantic: Runtime type validation with mypy support - validates data at runtime while mypy validates at compile time
mypy Version Compatibility: This skill covers mypy 1.8+ and reflects current best practices for Python type checking in 2025.
使用mypy时,可考虑以下互补技能(技能库中已提供):
- pytest: 带mypy的类型安全测试 - 将类型检查集成到测试套件中,实现全面的类型覆盖
- fastapi-local-dev: 全类型安全的FastAPI - 结合FastAPI的运行时验证与mypy的静态检查
- pydantic: 支持mypy的运行时类型验证 - 运行时验证数据,同时mypy在编译时验证类型
mypy版本兼容性: 本技能覆盖mypy 1.8+,反映了2025年Python类型检查的当前最佳实践。