只描述,不强制
类型提示是你附加在某个名字上的说明,通常是函数参数,表示“这里应该是 int”“这里返回一个字符串列表”之类。Python 在运行时不会检查它们。在标注为 int 的地方传入字符串,并不会报错。读取这些提示、在代码运行前提醒你的,是你的编辑器和外部工具(mypy、pyright,以及 VS Code 中基于 Pyright 的 Pylance)。
最简单的例子:
name: str 为参数添加注解,-> str 为返回值添加注解。两次调用都能运行。第二次调用是错的,静态类型检查器会把它标出来,但 Python 本身照样执行,因为 42 恰好支持 f"{...}" 插值。
这就是最关键的理解:类型提示是机器能读懂的文档,它们不会改变运行时的行为。
何必费这个事?
三个实实在在的好处,按见效快慢排列:
- 编辑器变得更聪明。 自动补全会列出正确的方法,重命名能正确地传播到各处,鼠标悬停在变量上就能看到它的类型。
- 函数签名能自我说明。
def fetch(url: str, timeout: float = 5.0) -> dict:清楚地告诉读者该传入什么、会得到什么,不用去读函数体。 - 类型检查器在运行代码之前就能发现错误。 在项目上运行
mypy .,能揪出单元测试常常漏掉的 bug:在你期待有值的地方返回了None,在该用列表的地方用了字典。
如果只是一个单文件脚本,只有你自己用,用完就扔,那就别加类型提示。只要是你以后还会回来看或者要分享给别人的代码,写提示花的那十几秒,一小时内就能赚回来。
基本的内置类型
下面这些都不需要导入:
变量注解(name: str = "Rosa")很少有必要,Python 会根据右边的值推断类型。把注解留给参数、返回类型,以及偶尔推断出的类型有歧义的情况。
不返回任何东西的函数用 -> None:
列表、字典、元组和集合
容器还需要第二条信息:里面装的是什么。现代 Python 允许直接对内置类型使用下标:
把它们念出来:
list[float]:浮点数列表。dict[str, int]:键为字符串、值为整数的字典。tuple[float, float]:恰好包含两个浮点数的元组。set[str]:字符串集合。
list[...]、dict[...] 这种下标写法从 Python 3.9 开始支持。在旧代码中,你会看到从 typing 导入的 List、Dict、Tuple:含义相同,只是旧写法。
可选值
“可能是 None”的情况很常见。它有两种等价的写法,都可以用,但新写法更好读:
str | None 表示“一个字符串,或者 None”。| 语法从 Python 3.10 开始支持。在旧代码中,你会看到 typing 模块的 Optional[str],含义相同。
调用方看到 -> str | None,就知道在使用结果之前要先检查是不是 None,这正是这个注解的意义所在。
联合类型:这个或那个
当一个值可能是几种类型之一时,用 |:
联合的类型可以不止两个。int | str | float 表示“这三种中的任意一种”。
为函数内部的变量添加注解
大多数时候,Python 能根据初始值推断出局部变量的类型。只有在以下情况才需要注解:
- 容器一开始是空的,类型检查器猜不出里面装什么。
- 值可能是多种类型,而你想固定为其中一种。
- 你想向阅读代码的人说明意图。
typing.Any 是逃生通道,意思是“这里我不想精确注解”。要少用。滥用 Any 会让其余的类型提示形同虚设。
为类添加注解
类属性和方法签名的注解方式与普通函数一样:
数据类(dataclass)其实必须有类型注解:@dataclass 装饰器会读取它们来生成 __init__ 和 __repr__。这是注解唯一会影响运行时行为的地方。
元组与“任意长度”的情况
tuple[...] 有两种形式,常让新手困惑:
tuple[float, float]:恰好两个浮点数。tuple[int, ...]:任意数量的整数。...(类型系统中真实存在的语法元素)表示“依此类推”。
可调用对象与类型别名
当函数接收或返回另一个函数时,用 Callable:
Callable[[int], int] 表示“接收一个 int 并返回一个 int 的函数”。
当某个注解反复出现时,给它起个名字:
别名只是普通的 Python 赋值语句。凡是能用长写法的地方,都可以用这个短名字。
运行类型检查器
Python 解释器本身会忽略类型提示。要真正检查它们,需要安装类型检查器。mypy 是最早的一个;pyright(VS Code 的 Pylance 用的就是它)速度更快。
pip install mypy
mypy your_project/
第一次运行时,会在你没想到的地方报出错误。一点一点地修复它们;需要先往前推进时,# type: ignore 可以让单独一行不再报错。
现代 IDE 会在你编辑时持续进行类型检查,所以大部分反馈在你保存之前就出现了。
不适合用类型提示的情况
- 快速探索性的脚本。 对只存在一小时的代码来说,注解只会增加负担。
- 高度动态的代码。 元编程、插件系统等模式,常常超出类型系统能描述的范围。给外层 API 加注解,内部保持宽松就好。
- 没有类型信息的第三方库。 如果你导入的库没有类型信息,
Any就会渗进你的代码。这没关系:那本来就不是你该注解的代码。
除此之外的情况,类型提示都是一个投入小、回报大的习惯。代价是每个函数签名多敲几个字符,回报是更少的 bug、更轻松的重构,以及能自我说明的代码。
下一篇:模块与导入
函数层面的工具你已经全部掌握了:参数、装饰器、类型提示。接下来我们看看 Python 如何把代码组织到多个文件中:模块、包和导入系统。
常见问题
Python 中的类型提示是什么?
类型提示是一种注解,用来说明变量、函数参数和返回值应该是什么类型。Python 本身在运行时不会强制检查它们:类型提示是给工具(IDE、代码检查工具,以及 mypy、pyright 等类型检查器)和阅读代码的人看的。
类型提示能让 Python 运行得更快吗?
不能。Python 解释器在运行时会忽略类型提示。提速发生在你的开发过程中:编辑器能发现更多笔误,函数签名更清晰,重构更安全。
什么时候应该加类型提示?
给公开函数的签名加上:参数和返回类型。在函数体内少加,只在变量类型不明显的地方加。一次性脚本可加可不加。对于共享代码和库,类型提示很快就能收回成本。