1536 字
8 分钟
Python 类型注解完全指南 2026:mypy 静态检查 + Pydantic 数据验证 + 泛型实战
Python 是动态类型语言,但从 3.5 开始引入类型注解,配合 mypy / pyright 可以在运行前发现大量类型错误。良好的类型注解不只是给 IDE 看的——它是代码最好的文档,也是重构时最可靠的安全网。
本文覆盖 2026 年 Python 类型注解的完整最佳实践。
快速决策:选哪种类型工具?
| 需求 | 推荐工具 |
|---|---|
| 运行时 API 数据验证 | Pydantic v2 |
| 静态类型检查(CI 门禁) | mypy(严格模式) |
| IDE 类型推断 | pyright(VS Code 内置 Pylance) |
| 配置文件管理(.env) | pydantic-settings |
| 复杂泛型 / 接口约束 | Protocol + TypeVar |
一、基础语法(Python 3.10+ 推荐写法)
# ── 变量注解 ──────────────────────────────────────────────────name: str = "Alice"age: int = 30
# 3.10+ 新语法:| 代替 Union,更简洁def greet(name: str | None = None) -> str: return f"Hello, {name or 'World'}"
# 3.9+ 内置容器直接用小写(无需 from typing import List)def process(items: list[int], config: dict[str, str]) -> tuple[int, str]: return items[0], config.get("key", "")
# 3.12+ type 别名语法type Vector = list[float]type Matrix = list[list[float]]
# ── 函数注解 ──────────────────────────────────────────────────from collections.abc import Callable, Sequence, Iterator
def apply(func: Callable[[int], str], value: int) -> str: return func(value)
def first(items: Sequence[int]) -> int | None: return items[0] if items else None
# *args 和 **kwargsdef log(*messages: str, level: str = "INFO") -> None: pass
def create(**kwargs: str | int) -> dict[str, str | int]: return kwargs二、TypeVar 与 Generic(泛型)
2.1 泛型函数
from typing import TypeVar
T = TypeVar("T")
def first_item(items: list[T]) -> T | None: return items[0] if items else None
# mypy 正确推断返回类型n = first_item([1, 2, 3]) # int | Nones = first_item(["a", "b"]) # str | None
# 有上界约束from numbers import NumberNumT = TypeVar("NumT", bound=Number)
def double(x: NumT) -> NumT: return x * 2 # type: ignore[return-value]2.2 泛型类
from typing import Generic
K = TypeVar("K")V = TypeVar("V")
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: if not self._items: raise IndexError("Stack is empty") return self._items.pop()
def __len__(self) -> int: return len(self._items)
# 使用时 mypy 能完整推断类型int_stack: Stack[int] = Stack()int_stack.push(1)value = int_stack.pop() # mypy 推断为 int
# 双类型参数class Pair(Generic[K, V]): def __init__(self, key: K, value: V) -> None: self.key = key self.value = value
def swap(self) -> "Pair[V, K]": return Pair(self.value, self.key)三、Protocol(结构子类型)
from typing import Protocol, runtime_checkable
# 定义协议接口(不需要显式继承,鸭子类型)@runtime_checkableclass Drawable(Protocol): def draw(self) -> None: ... def resize(self, factor: float) -> None: ...
class Circle: def draw(self) -> None: print("Drawing circle") def resize(self, factor: float) -> None: self.radius *= factor
class Square: def draw(self) -> None: print("Drawing square") def resize(self, factor: float) -> None: self.side *= factor
# Circle / Square 无需继承 Drawable,mypy 自动识别def render_all(shapes: list[Drawable]) -> None: for shape in shapes: shape.draw()
render_all([Circle(), Square()]) # mypy ✅print(isinstance(Circle(), Drawable)) # True(@runtime_checkable)Protocol vs ABC:ABC 需要显式继承(名义子类型);Protocol 只要结构匹配即可(结构子类型)。第三方库的类无法修改时,用 Protocol 约束更灵活。
四、TypedDict / Literal / TypeGuard
from typing import TypedDict, NotRequired, Literal, Final, TypeGuard
# ── TypedDict ─────────────────────────────────────────────────class UserDict(TypedDict): id: int name: str email: str age: NotRequired[int] # 可选字段
user: UserDict = {"id": 1, "name": "Alice", "email": "a@b.com"} # ✅# user = {"id": "1", ...} # ❌ mypy
# ── Literal:限制值域 ─────────────────────────────────────────LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR"]
def log(message: str, level: LogLevel = "INFO") -> None: pass
log("msg", "INFO") # ✅# log("msg", "VERBOSE") # ❌ mypy 报错
# ── Final:常量 ───────────────────────────────────────────────MAX_RETRIES: Final = 3API_BASE: Final[str] = "https://api.example.com"
# ── TypeGuard:自定义类型收窄 ─────────────────────────────────def is_string_list(val: list[object]) -> TypeGuard[list[str]]: return all(isinstance(item, str) for item in val)
def process(items: list[str] | list[int]) -> None: if is_string_list(items): joined = ", ".join(items) # mypy 收窄为 list[str],✅五、Self 类型(Python 3.11+)
from typing import Self
class Builder: def __init__(self) -> None: self._config: dict[str, str] = {}
def set(self, key: str, value: str) -> Self: # Self 指向实际类型 self._config[key] = value return self
def build(self) -> dict[str, str]: return self._config
class AdvancedBuilder(Builder): def validate(self) -> Self: return self
# 子类继承时,Self 自动指向 AdvancedBuilder,链式调用类型正确result = AdvancedBuilder().set("k", "v").validate().build() # ✅六、mypy 配置
[tool.mypy]python_version = "3.12"strict = true # 开启所有严格检查(新项目推荐)plugins = ["pydantic.mypy"] # Pydantic 插件
# strict 包含:warn_return_any / disallow_untyped_defs# / disallow_any_generics / check_untyped_defs 等
[[tool.mypy.overrides]]module = ["some_untyped_lib.*"]ignore_missing_imports = true # 无类型存根的第三方库uv run mypy src/ # 检查整个项目uv run mypy src/main.py # 检查单个文件
# 跳过单行(尽量少用)result = func() # type: ignore[attr-defined]七、Pydantic v2 实战
from pydantic import BaseModel, Field, field_validator, model_validatorfrom pydantic import ConfigDict
class User(BaseModel): model_config = ConfigDict(strict=True, frozen=True)
id: int name: str = Field(min_length=1, max_length=100) email: str age: int | None = Field(default=None, ge=0, le=150) tags: list[str] = Field(default_factory=list)
# 创建 & 序列化user = User(id=1, name="Alice", email="alice@example.com")user.model_dump() # → dict(v2 写法,v1 是 .dict())user.model_dump_json() # → JSON 字符串User.model_validate({"id": 1, "name": "Alice", "email": "a@b.com"})
# ── 字段验证器 ────────────────────────────────────────────────class Article(BaseModel): title: str tags: list[str]
@field_validator("title") @classmethod def strip_title(cls, v: str) -> str: if not v.strip(): raise ValueError("title cannot be empty") return v.strip()
@field_validator("tags", mode="before") # 预处理阶段 @classmethod def parse_tags(cls, v: str | list[str]) -> list[str]: if isinstance(v, str): return [t.strip() for t in v.split(",") if t.strip()] return v
# ── 模型级验证器 ──────────────────────────────────────────────class DateRange(BaseModel): start: str end: str
@model_validator(mode="after") def check_order(self) -> "DateRange": if self.start >= self.end: raise ValueError("end must be after start") return self
# ── 嵌套模型 ──────────────────────────────────────────────────class Address(BaseModel): city: str country: str = "CN"
class Company(BaseModel): name: str address: Address # 嵌套模型自动转换 employees: list[User]
company = Company( name="Example Corp", address={"city": "Beijing"}, # dict 自动转 Address ✅ employees=[{"id": 1, "name": "Alice", "email": "a@b.com"}],)7.1 pydantic-settings 配置管理
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", case_sensitive=False)
app_name: str = "MyApp" debug: bool = False database_url: str # 必填(无默认值) redis_url: str = "redis://localhost:6379" secret_key: str
from functools import lru_cache
@lru_cache # 单例,避免重复读取 .envdef get_settings() -> Settings: return Settings()7.2 FastAPI 完整集成
from fastapi import FastAPI, Dependsfrom pydantic import BaseModel, ConfigDict
app = FastAPI()
class CreateUserRequest(BaseModel): name: str email: str age: int | None = None
class UserResponse(BaseModel): model_config = ConfigDict(from_attributes=True) # ORM 兼容 id: int name: str email: str
@app.post("/users", response_model=UserResponse, status_code=201)async def create_user(request: CreateUserRequest) -> UserResponse: user = await db_create_user(request.model_dump()) return UserResponse.model_validate(user) # ORM → Pydantic八、mypy 常见错误速查
| 错误信息 | 解决方法 |
|---|---|
Item "None" of "X | None" has no attribute "y" | if x is not None: 判断后再访问 |
Argument 1 has incompatible type "str"; expected "int" | 检查调用处实际传入类型 |
Function is missing a return type annotation | 添加 -> ReturnType |
Missing type parameters for generic type "list" | 改为 list[int] 等具体类型 |
Module "xxx" has no attribute "yyy" | 安装 types-xxx 或 ignore_missing_imports |
相关文章:
- Python 项目工程化实战:pyproject.toml + uv + Ruff
- FastAPI 完全指南 2026:从零构建高性能异步 Python API
- Python 异步编程完全指南 2026:asyncio + aiohttp + 并发
- Redis 完全指南 2026:数据结构 + 缓存设计 + 持久化
- Git 进阶实战完全指南:hooks + 工作流 + 多账号
本文基于 Python 3.12 / mypy 1.10 / Pydantic v2.8 验证。
type别名新语法需要 Python 3.12+;Self需要 Python 3.11+。
Python 类型注解完全指南 2026:mypy 静态检查 + Pydantic 数据验证 + 泛型实战
https://971918.xyz/posts/python-guide/python-type-hints-guide/