Python类型注解(Type Hint)
一. 类型注解介绍
众所周知,Python是一门动态类型语言。与C、C++等静态类型语言不同,在Python中,定义变量不需要声明类型,一个变量上一秒是整型,下一秒就可以是字符串。
这种灵活的特性可以给我们带来极大的便利,但也有一些缺点:我们没法一眼看出这个变量是什么类型、这个函数的返回值是什么类型、应该传什么类型的参数等等,必要时还需要自己进入函数去研究它的源代码。由类型引发的错误可能会随着代码量的上升而增加。💩
1 | def divide(num): |
为了解决这个问题,Python从3.5版本逐渐引入了类型注解的特性。它可以让你在写Python代码时可选地为变量、参数、返回值标注类型。
类型注解的优势:
- 可以提高代码的可读性和可维护性;
- 在运行前就能通过静态类型检查器检测出类型导致的错误;
- 让代码编辑器提供更全面的智能补全功能。
偷懒这一块
二. 基础类型
1 | # 变量 |
三. 容器类型
注:typing模块自Python 3.5引入,用于支持类型注解,包含了丰富的类型注解工具。
1. 列表
1 | from typing import List |
2. 元组
1 | from typing import Tuple |
3. 字典
1 | from typing import Dict |
4. 集合
1 | from typing import Set |
可以直接使用内置容器类型进行注解(但内置容器类型后直接加方括号的语法直到Python 3.9后才支持),如:
1 | def get_average(scores: list[int]) -> float: |
四. 进阶类型
1. 多种类型
Union表示一个值可以是多种类型中的一种。
1 | from typing import Union |
自Python 3.10版本起可以使用|语法代替:
1 | def f(x: int | None) -> int: |
2. 可选类型
Optional[T]等价于Union[T, None],表示一个值可以是类型T或者None。
1 | from typing import Optional |
3. 任意类型
Any表示可以是任意类型。
1 | from typing import Any |
避免过度使用Any,尽可能使用具体的类型来注解。
4. 无返回值类型
NoReturn表示不会正常返回的函数。
1 | from typing import NoReturn |
5. 可调用类型
Callable表示可以被调用的类型。
高阶函数:
1 | from typing import Callable |
Callable可以做进一步的定义,即这个可调用对象的参数类型和返回值类型。
第一个是参数类型,需要使用方括号包裹,第二个是返回值类型。
装饰器:
1 | from typing import Callable |
6. 字面量类型
Literal表示必须是某个具体的值,而不仅仅是某个类型。
1 | from typing import Literal |
对于:
1 | from typing import Literal |
报错:
1 | error: Argument 2 to "Person" has incompatible type "str"; expected "Literal['male', 'female']" [arg-type] |
因为Python只知道g是一个字符串,而不知道它是具体的"female"这个值。
此时需要注解g:
1 | g: Literal["male", "female"] = "female" |
7. 泛型
泛型表示可重用的类型模板:
1 | from typing import TypeVar |
T表示任意类型,但是函数参数a、b和返回值类型必须都是这个类型。
可以进一步约束类型,限制T是整数、浮点数或者字符串:
1 | T = TypeVar('T', int, float, str) |
Python 3.12提供了简化语法:
1 | def add_two_items[T: (int, float, str)](a: T, b: T) -> T: |
五. 类型别名
类型注解本身也是一个对象,因此我们可以为复杂类型创建别名来提高可读性,例如:
1 | from typing import Tuple |
六. 自定义类型
给类型起别名并不会创建一个新的类型。对于人类而言,别名和原名具有不同的逻辑含义,但在类型检查器眼中,它们本质上是同一个类型。所以如果你不小心混用了这些名称,类型检查器无法检测出这种逻辑上的错误:
1 | UserId = int |
虽然写错了,但由于order_id、UserId和int被视为同一个东西,类型检查器无法检测出这个漏洞。要解决这个问题,必须创建真正的自定义类型,将int、UserId、OrderId三者区分开来。
NewType可以创建真正的自定义类型,语法为:新类型名称 = NewType('新类型名称', 自定义类型):
1 | from typing import NewType |
现在静态类型检查器可以检测出问题:
1 | test.py:15: error: Argument 1 to "delete_user" has incompatible type "OrderId"; expected "UserId" [arg-type] |
要使用NewType创建的新类型,需要显式地将值转换成这个新类型,否则会被视为两个不一样的类型。
在上面的例子中,必须用UserId(1)和OrderId(123)将1和123转化成UserId和OrderId,因为int、UserId、OrderId三者均被视为不同的类型。
如果写成:
1 | user_id = 1 |
就会爆炸:
1 | test.py:15: error: Argument 1 to "delete_user" has incompatible type "int"; expected "UserId" [arg-type] |
七. 静态类型检查器
静态类型检查器是一种在代码运行前进行类型分析的工具。
mypy是一个流行的静态类型检查工具。
安装:
1 | pip install mypy |
运行类型检查:
1 | mypy *.py |
VS Code内置了Pyright作为其类型检查工具(需要安装Pylance扩展),开启方式为:
-
点击右下角状态栏花括号,点击“选择类型检查模式”:

-
选择模式:
