什么是类型注解?
类型注解就是在 Python 代码中,为变量、函数参数、函数返回值等添加类型信息“标注”。

它本身不会改变代码在运行时的行为,Python 解释器在执行代码时通常会忽略这些注解,它的主要作用是:
- 提高代码可读性:其他开发者(或者未来的你)在阅读代码时,能立刻明白一个变量应该是什么类型,一个函数期望接收什么类型的参数,以及会返回什么类型。
- 静态类型检查:可以使用像
mypy这样的静态类型检查工具,在代码运行前就发现类型错误,从而避免很多潜在的 Bug。 - 辅助 IDE/编辑器:现代的 IDE(如 PyCharm, VS Code)可以利用类型注解提供更强大的代码补全、错误提示和重构功能。
为什么需要类型注解?(动机)
在 Python 3.5 之前,Python 是一种动态类型语言,变量的类型在运行时才确定,这带来了灵活性,但也带来了问题:
# 动态类型的“坑”
def process_data(data):
# 你期望 data 是一个数字列表
return sum(data) / len(data)
# 这段代码能正常工作
print(process_data([1, 2, 3, 4])) # 输出: 2.5
# 但如果传入了错误类型,运行时才会报错
print(process_data("hello")) # 报错: TypeError: unsupported operand type(s) for +: 'int' and 'str'
类型注解就是为了解决这类问题而生的,它让我们能够“声明”我们的意图:
from typing import List # 需要从 typing 模块导入 List
def process_data(data: List[int]) -> float:
"""
接收一个整数列表,返回其平均值。
:param data: 一个包含整数的列表
:return: 平均值,浮点数
"""
return sum(data) / len(data)
# IDE 和 mypy 现在可以立刻检查出问题
# process_data("hello") # mypy 会在这里报错:Argument "data" to "process_data" has incompatible type "str"; expected "List[int]"
如何使用类型注解?
类型注解的语法非常直观。

1 变量注解
直接在变量名后加上冒号 和类型。
name: str = "Alice" age: int = 30 height: float = 1.75 is_student: bool = False # 如果变量没有初始值,可以使用 ... 或 None id: int = ...
2 函数注解
函数注解分为两部分:参数注解和返回值注解。
- 参数注解:在参数名后加 和类型。
- 返回值注解:在函数参数列表后的
->和类型。
def greet(name: str) -> str:
return f"Hello, {name}!"
# 带有默认值的参数
def log(message: str, level: str = "INFO") -> None:
print(f"[{level}] {message}")
3 复杂类型注解
当涉及到更复杂的数据结构时,就需要使用 typing 模块中的工具。
List, Tuple, Dict, Set
List[T]: T 类型的列表。Tuple[T1, T2, ...]: 固定类型的元组。Dict[K, V]: 键类型为 K,值类型为 V 的字典。Set[T]: T 类型的集合。
from typing import List, Tuple, Dict, Set
# 一个包含字符串的列表
usernames: List[str] = ["alice", "bob", "charlie"]
# 一个包含两个元素的元组:(int, str)
point: Tuple[int, int] = (10, 20)
# 一个字典,键是字符串,值是整数
inventory: Dict[str, int] = {"apple": 10, "banana": 5}
# 一个包含整数的集合
unique_ids: Set[int] = {1, 2, 3, 4, 5}
Optional 和 Union
Optional[T]: 等同于Union[T, None],表示一个变量可以是 T 类型,也可以是None。Union[T1, T2, ...]: 表示一个变量可以是多种类型中的任意一种。
from typing import Optional, Union
def get_user_name(user_id: int) -> Optional[str]:
# ... 从数据库查询 ...
if user_id == 1:
return "Alice"
else:
return None # 返回类型是 str 或 None
def process(value: Union[int, str]) -> str:
if isinstance(value, int):
return f"Number: {value}"
else:
return f"String: {value}"
Any
Any 表示“任何类型”,当你不确定或者不关心一个变量的具体类型时,可以使用它,这会关闭对该变量的类型检查。

from typing import Any
def unknown_function(data: Any) -> Any:
# 可以对 data 做任何操作
print(data)
return 42
unknown_function("hello")
unknown_function(123)
unknown_function([1, 2, 3])
Callable
表示可调用对象,比如函数。
from typing import Callable
def apply_operation(x: int, y: int, op: Callable[[int, int], int]) -> int:
"""对两个整数应用一个操作函数"""
return op(x, y)
def add(a: int, b: int) -> int:
return a + b
def multiply(a: int, b: int) -> int:
return a * b
result = apply_operation(5, 3, add) # 正确
# result = apply_operation(5, 3, "add") # mypy 会报错,因为 "add" 不是可调用对象
Python 版本演进
类型注解的语法在不同 Python 版本中有所变化。
Python 3.5 - 3.8: 使用 typing 模块
这是最经典的用法,至今仍然广泛使用,因为它兼容所有现代 Python 版本。
# Python 3.5+
from typing import List, Dict
def get_scores() -> Dict[str, List[int]]:
return {"math": [90, 85], "english": [88, 92]}
Python 3.9+: 内置泛型类型(重要更新!)
从 Python 3.9 开始,标准类型(如 list, dict, tuple, set)可以直接用作泛型,不再需要从 typing 模块导入,这使得代码更简洁、更符合直觉。
# Python 3.9+
# 不再需要 from typing import List, Dict
def get_scores() -> dict[str, list[int]]: # 直接使用内置类型
return {"math": [90, 85], "english": [88, 92]}
# 旧的写法 (Python 3.9+ 仍然支持,但不推荐)
# def get_scores() -> Dict[str, List[int]]:
# 一些旧的类型如 Tuple, Union, Optional 仍需从 typing 导入
from typing import Union, Optional
def process(value: Union[int, str]) -> Optional[str]:
# ...
pass
Python 3.12+: PEP 698 (Type Parameter Syntax - 实验性)
这是一个更进一步的简化,允许你直接在函数上定义类型参数,类似于 C++ 或 Rust 的模板语法,目前仍是实验性功能。
# Python 3.12+ (需要启用 --experimental-flags)
typealias ListOrSet[T] = list[T] | set[T]
def get_items() -> ListOrSet[int]:
return {1, 2, 3}
如何利用类型注解?(类型检查器)
光有注解没用,你需要工具来“阅读”它们并进行检查。
mypy - 最流行的静态类型检查器
-
安装 mypy:
pip install mypy
-
准备一个有类型错误的 Python 文件
example.py:from typing import List def get_average(numbers: List[int]) -> float: return sum(numbers) / len(numbers) # 故意传入一个字符串列表 my_list = ["1", "2", "3"] result = get_average(my_list) # 这里有类型错误 print(result) -
运行 mypy 检查:
mypy example.py
-
查看输出:
example.py:10: error: Argument "numbers" to "get_average" has incompatible type "List[str]"; expected "List[int]" Found 1 error in 1 file (checked 1 source file)mypy在代码运行前就发现了这个潜在的类型错误,避免了程序崩溃或产生非预期结果。
IDE 集成
现代 IDE 已经内置了类似 mypy 的检查功能,你可以在编码时就看到实时的错误提示,无需手动运行命令。
最佳实践和总结
-
何时使用:
- 新项目: 强烈推荐从一开始就使用类型注解。
- 大型项目/团队: 几乎是必需品,能极大提升协作效率。
- 库开发: 必须使用,这是库质量的重要保证。
- 小型脚本/快速原型: 可以不使用,以保持灵活性。
-
渐进式采用:
你不必一次性为整个项目添加类型注解,可以从新函数、新模块开始,逐步为旧代码添加。
-
typingvs 内置类型:- 如果你的项目最低支持 Python 3.9+,优先使用内置的
list,dict等。 - 如果需要兼容 Python 3.8 或更低版本,或者需要使用
Union,Optional,Callable等非泛型类型,则必须从typing模块导入。
- 如果你的项目最低支持 Python 3.9+,优先使用内置的
-
Any是双刃剑:- 过度使用
Any会失去类型检查带来的好处,只在确实无法确定类型时使用。
- 过度使用
-
保持一致性:
在一个项目中,尽量保持类型注解风格的一致性。
Python 的类型注解是一项为静态类型检查服务的语法特性,它通过在代码中显式地标注类型,借助 mypy 等工具,在开发阶段就捕获类型相关的错误,从而让 Python 代码更健壮、更易于维护。
