类型注解解决什么

Python 是动态类型语言,函数能接收任何类型的参数,这也意味着错误往往到运行那一刻才暴露。类型注解的价值在于把「约定」写进代码:参数是什么类型、返回值是什么类型,人和工具都能看懂。

mypy 这类静态检查工具会在运行之前扫描代码,把类型不匹配的问题直接指出来。类型注解不是给 Python 增加强制类型,而是让动态语言在关键边界上变得可验证,既保留灵活,又降低出错概率。

项目越大、协作者越多,类型注解带来的收益越明显。接口边界、数据模型、公共函数这三类位置最值得优先加注解,内部实现细节反而可以从容一点。

类型注解是写给人的文档,也是写给机器的检查单。
本文核心知识点
  • 注解语法变量与函数签名写法。
  • typing容器、可选与联合类型。
  • 泛型复用类型逻辑。
  • mypy静态检查与配置。
Python 类型注解配图:蓝调抽象插画中半透明标签卡片层叠与发光网格光网的扁平科技场景
标签、网格、连线,类型注解就像给数据贴上可追踪的标签。

变量与函数基础注解

变量注解用冒号声明类型,函数注解在参数后加冒号、在函数末尾用箭头声明返回值类型。最简单的写法只涉及几个内建类型:int、float、str、bool。

name: str = "吉极云码"
count: int = 42

def greet(name: str, times: int = 1) -> str:
    return (name + "!") * times

函数注解尤其重要,它是模块对外接口的契约。团队协作时,别人看到签名就知道该传什么、会得到什么,不用钻进函数体里读实现。

要注意注解只是声明,不会在运行时强制校验。类型检查要靠 mypy 这类工具在开发阶段完成,运行时如果确实需要校验,再配合 Pydantic 这类库做数据验证。

容器、可选与联合类型

真实代码里更多的是列表、字典、可能为空的返回值。typing 模块提供了对应工具:List、Dict、Optional、Union,Python 3.10 之后还能用内建的 list、dict 加泛型语法。

from typing import Optional

scores: list[int] = [90, 85, 78]
lookup: dict[str, int] = {"a": 1, "b": 2}

def find_user(uid: int) -> Optional[str]:
    return "用户" if uid > 0 else None

Optional[str] 等价于 str 或 None,明确告诉调用方返回值可能为空,调用前必须先判断,否则 mypy 会报错。联合类型 Union[str, int] 在 3.10 之后可直接写作 str | int,代码更简洁。

写法含义
list[int]整数列表
dict[str, int]键为字符串、值为整数的字典
Optional[str]字符串或 None
str | int字符串或整数

泛型与协议

有些函数不在乎容器里装的是什么类型,只在乎容器本身的操作。这时候用泛型把类型参数化,函数就能复用到任意类型上,同时保留类型信息。

from typing import TypeVar

T = TypeVar("T")

def first(items: list[T]) -> T:
    return items[0]

value = first([1, 2, 3])   # 推断为 int

协议(Protocol)则定义结构上的要求:只要对象有协议要求的属性和方法,就视为满足该类型。这让类型系统贴近「鸭子类型」的直觉,同时把结构契约写进检查里。

泛型和协议是进阶能力,先把基础注解用熟练,再逐步引入这两样,避免一上来把代码写得难以读懂。

mypy 接入与配置

mypy 是最流行的 Python 静态类型检查器。安装后直接运行,它读取注解并按配置的严格程度检查代码。建议在项目根放一个配置文件,把严格级别和忽略规则集中管理。

[mypy]
python_version = 3.12
strict = true
ignore_missing_imports = true
warn_unused_ignores = true

strict 会开启一整套严格检查:变量必须有类型、参数必须有注解、返回值必须标注。一开始太严格会劝退团队,建议从基础配置起步,在 CI 里逐步提高要求。

把 mypy 放进 CI 流水线,作为合并请求的检查门槛,类型问题就不会流进主干。配合编辑器插件,写代码时就能实时看到类型错误,反馈成本最低。