Menu

Python 类型提示:为函数、列表、字典等添加注解

Python 类型提示是什么、什么时候有用,以及为变量、函数签名、容器和可选值添加注解的语法。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

只描述,不强制

类型提示是你附加在某个名字上的说明,通常是函数参数,表示“这里应该是 int”“这里返回一个字符串列表”之类。Python 在运行时不会检查它们。在标注为 int 的地方传入字符串,并不会报错。读取这些提示、在代码运行前提醒你的,是你的编辑器和外部工具(mypy、pyright,以及 VS Code 中基于 Pyright 的 Pylance)。

最简单的例子:

name: str 为参数添加注解,-> str 为返回值添加注解。两次调用都能运行。第二次调用是错的,静态类型检查器会把它标出来,但 Python 本身照样执行,因为 42 恰好支持 f"{...}" 插值。

这就是最关键的理解:类型提示是机器能读懂的文档,它们不会改变运行时的行为。

何必费这个事?

三个实实在在的好处,按见效快慢排列:

  1. 编辑器变得更聪明。 自动补全会列出正确的方法,重命名能正确地传播到各处,鼠标悬停在变量上就能看到它的类型。
  2. 函数签名能自我说明。 def fetch(url: str, timeout: float = 5.0) -> dict: 清楚地告诉读者该传入什么、会得到什么,不用去读函数体。
  3. 类型检查器在运行代码之前就能发现错误。 在项目上运行 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 解释器在运行时会忽略类型提示。提速发生在你的开发过程中:编辑器能发现更多笔误,函数签名更清晰,重构更安全。

什么时候应该加类型提示?

给公开函数的签名加上:参数和返回类型。在函数体内少加,只在变量类型不明显的地方加。一次性脚本可加可不加。对于共享代码和库,类型提示很快就能收回成本。

Coddy 编程语言插图

和 Coddy 一起学编程

开始