mypy 默认只检查带类型注解的函数,未注解代码被静默跳过;需显式启用 disallow_untyped_defs 等严格配置并正确标注类型(如 list[int]、Awaitable[str]),才能真正发挥静态检查作用。

为什么 mypy 能提前发现类型错误,但你的项目里它却“没报错”?
多数人装了 mypy 却没真正用起来,根本原因是:它默认只检查带类型注解的函数,对纯动态代码静默跳过。也就是说,如果你没写 def foo(x: int) -> str:,mypy 就当这段代码不存在。
- 必须显式添加类型注解,
mypy才会介入校验;不加注解 ≠ 无类型,而是“不检查” -
mypy不运行代码,也不修改运行时行为——它只读源码,输出潜在类型冲突 - 项目根目录下没有
mypy.ini或pyproject.toml配置时,它用极宽松的默认策略,容易漏检 - 常见误操作:在未启用
--check-untyped-defs时,把未注解函数当“安全”,结果参数传None进去才在 runtime 崩
mypy 检查失败但程序能跑,该信谁?
这是最常让人犹豫的点:mypy 报 error: Argument 1 to "process" has incompatible type "str"; expected "int",可你本地一跑,Python 确实没报错。这时候要信 mypy。
- Python 的鸭子类型允许“暂时凑合”,但一旦数据来源变化(比如 API 返回字段类型变更、数据库字段改了类型),这类错误就会在生产环境爆发
-
mypy的检查基于类型协议和调用上下文推导,比人工 review 更早暴露隐含契约断裂 - 特别注意
Optional[T]和Union[T, None]的区别:前者明确允许None,后者若漏写None分支,mypy会警告,而 Python 运行时可能只在特定路径触发AttributeError - 避免用
# type: ignore掩盖问题,除非你同时补了单元测试覆盖该分支,并加了 TODO 注释说明为何绕过
如何让 mypy 在 CI 中真正起作用,而不是变成摆设?
很多团队把 mypy 加进 CI 后,发现 PR 里一堆旧代码报错,于是临时加 --follow-imports=skip 或直接删掉检查步骤——这等于关掉警报器。
- 推荐渐进式接入:先用
mypy --new-style-report --show-error-codes .生成报告,按 error code(如arg-type、return)分类统计,优先修复高频 error code - 在
pyproject.toml中锁定严格级别:[tool.mypy] disallow_untyped_defs = true disallow_incomplete_defs = true disallow_untyped_decorators = true warn_return_any = true warn_unused_ignores = true
- CI 中别用
mypy *.py,而应指定包路径(如mypy src/ tests/),避免扫描 venv 或生成文件引入噪音 - 配合 pre-commit:在
.pre-commit-config.yaml里加入mypyhook,确保本地提交前就过一遍,不是等 CI 失败才回头改
哪些类型标注最容易写错,导致 mypy 误报或漏报?
类型系统不是越复杂越好,写错反而削弱可信度。几个高频陷阱:
立即学习“Python免费学习笔记(深入)”;
-
List和list混用:Python 3.9+ 应用内置list[int],而非typing.List[int];混用会导致mypy无法识别协变性 -
Dict[str, Any]是“类型黑洞”,几乎关闭所有检查;改用更精确的结构,如TypedDict或dataclass,尤其在处理 JSON 解析结果时 - 异步函数返回值写成
async def fetch() -> str:,实际返回的是Coroutine[Any, Any, str],必须显式标注为-> Awaitable[str]或-> Coroutine[...] - 对第三方库没提供 stub 的模块(如某些 SDK),不要硬写假类型,用
from typing import TYPE_CHECKING+if TYPE_CHECKING:块做条件导入,避免mypy因找不到定义而跳过整段逻辑
类型检查不是终点,而是把隐性假设显性化的过程。最难的从来不是写对 -> int,而是判断某个变量到底该是 Optional[str] 还是 Literal["a", "b"]——这需要你真正理解业务边界,而不是套模板。


















