10 类型注解与静态检查
大约 3 分钟编程语言Pythontypingmypy静态检查
10 类型注解与静态检查
类型注解的价值在边界:配置、请求、公共函数、跨模块数据和外部系统输入输出。给每个局部变量加注解通常没有收益。
1. 基础签名
from collections.abc import Iterable
def total(values: Iterable[int]) -> int:
return sum(values)
def normalize_name(name: str | None) -> str:
if name is None:
return "anonymous"
return name.strip() or "anonymous"Python 3.10 起可以使用 X | Y 表达联合类型。需要兼容更早版本时使用 Optional 和 Union。
2. 内置泛型
def unique(values: list[str]) -> dict[str, list[str]]:
grouped: dict[str, list[str]] = {}
for value in values:
grouped.setdefault(value[:1], []).append(value)
return groupedPython 3.9 起可以直接使用 list[str]、dict[str, int];更早版本通常需要 List 和 Dict。
3. TypedDict
from typing import TypedDict
class HealthPayload(TypedDict):
service: str
healthy: bool
latency_ms: int
def render(payload: HealthPayload) -> str:
return f"{payload['service']}: {payload['latency_ms']}ms"TypedDict 描述固定字段的字典形状。运行时仍是普通字典,不会自动校验外部 JSON。
4. Literal 与枚举
from typing import Literal
Severity = Literal["info", "warning", "critical"]
def emit(message: str, severity: Severity = "info") -> None:
print(severity, message)Literal 适合接口允许的少量固定字符串。值集合可能持续扩展时,使用普通字符串加运行时校验,或单独维护版本化枚举。
5. Protocol
from typing import Protocol
class Clock(Protocol):
def now(self) -> float:
...
def remaining(clock: Clock, deadline: float) -> float:
return max(0.0, deadline - clock.now())结构类型可以降低测试替身的继承负担。协议越小,复用范围越大。
6. Any、object 与 cast
object表示任何对象,但使用前必须收窄。Any表示放弃类型检查,会向周围扩散。cast(T, value)只影响静态检查,不执行运行时转换。
from typing import Any, cast
def parse_config(raw: dict[str, Any]) -> dict[str, int]:
timeout = raw.get("timeout", 5)
if not isinstance(timeout, int):
raise ValueError("timeout must be int")
return {"timeout": timeout}优先在边界完成运行时校验,再让内部代码使用较窄类型。不要用 cast 把未经验证的数据“说服”为安全。
7. 泛型函数
Python 3.12 起支持新的类型参数语法:
def first[T](values: list[T]) -> T:
if not values:
raise ValueError("values is empty")
return values[0]需要兼容旧版本时:
from typing import TypeVar
T = TypeVar("T")
def first_legacy(values: list[T]) -> T:
if not values:
raise ValueError("values is empty")
return values[0]8. mypy 与 pyright
典型 mypy 配置:
[tool.mypy]
python_version = "3.12"
strict = true
warn_unreachable = true
show_error_codes = true类型检查命令:
python -m mypy src tests大型存量项目不必一次开启全部严格规则。推荐顺序:
- 先覆盖公共接口和配置模型。
- 开启
check_untyped_defs和缺失注解检查。 - 关闭关键模块的
Any扩散。 - 再逐步启用
strict。
9. 运行时校验
类型注解不是输入校验器。来自 HTTP、文件、消息队列和命令行的数据必须经过:
- 类型和范围检查。
- 必填字段与默认值处理。
- 编码和长度限制。
- 业务约束。
- 未知字段策略。
可以使用手写校验、dataclass 构造校验或专门的 Schema 库。选择标准是维护成本、错误质量和数据规模,而不是只看声明是否简洁。
10. 常见陷阱
- 给所有变量标注,代码变得更长但没有增加边界信息。
- 大量
Any让静态检查失效。 - 把
cast当作运行时转换。 - 注解与
pyproject.toml声明的最低 Python 版本不一致。 - 只在本地 IDE 检查,没有进入 CI。
- 对外部 JSON 直接
TypedDict后使用。
