为什么要静态类型检查

注解光写不查等于没写。mypy不运行代码的情况下扫描,提前发现:

  • 把 None 当 str 用
  • 函数调用参数类型错
  • 字典 key 拼写错
  • 忘处理 None 分支

装 + 跑

pip install mypy
mypy myfile.py
mypy mypackage/

第一个例子

# bad.py
def greet(name: str) -> str:
    return f"hi {name}"

greet(42)          # 传了 int 不是 str
$ mypy bad.py
bad.py:4: error: Argument 1 to "greet" has incompatible type "int"; expected "str"
Found 1 error

常见报错与修法

1. Optional 没处理

def find(uid: int) -> str | None:
    return DB.get(uid)

name = find(1)
print(name.upper())     # mypy: Item "None" of "str | None" has no attribute "upper"

修复:

name = find(1)
if name is not None:
    print(name.upper())

2. Any 像传染病

import json
data = json.loads("...")        # data 是 Any
result = data.upper()            # 通过!但运行可能崩

加上 TypedDict 或显式类型:

from typing import TypedDict

class Config(TypedDict):
    name: str
    port: int

data: Config = json.loads(...)

3. 容器内类型不一致

nums: list[int] = [1, 2, "3"]    # mypy: Incompatible types

配置文件 mypy.ini

[mypy]
python_version = 3.12
strict = True
warn_unused_ignores = True
warn_redundant_casts = True

# 第三方库没有类型时不报警
[mypy-some_lib.*]
ignore_missing_imports = True

strict = True 启用最严格的所有检查。

# type: ignore 暂时忽略

import flaky_lib    # type: ignore[import]

result = call_external()    # type: ignore   # 知道有问题,但暂时跳过

reveal_type:调试神器

x = some_complex_expression
reveal_type(x)        # mypy 会打印推断出的类型

只在 mypy 检查时有效,运行时会报错——所以临时用,用完删掉。

pyright:另一个选择

pyright 是微软出的,集成在 VS Code 的 Pylance 插件里。比 mypy 快,编辑器实时报错。

pip install pyright
pyright myfile.py

VS Code 用户:装 Pylance 插件就内置了。

渐进式接入

老项目不可能一次全标。策略:

  1. 先开 --strict 跑新模块
  2. 老模块用 # type: ignore 整个文件忽略
  3. 一个文件一个文件地加注解

实际收益

  • IDE 自动补全更准
  • 重构时改了 A 函数,调用方有问题立刻看到
  • 团队协作减少"这个 None 怎么来的"扯皮

下一篇讲 dataclass / TypedDict / NamedTuple——现代数据载体。