Python 是动态类型语言,变量本身没有固定类型,但自从 Python 3.5 引入类型提示(Type Hints)后,我们可以自愿地给代码加上类型标注。它们不影响运行时,却能让代码意图更清晰,配合静态检查工具(如 mypy、pyright)能在运行前发现大量类型错误。
变量类型标注
最简单的标注:在变量名后加冒号和类型。
name: str = "Alice"
age: int = 30
is_active: bool = True
对于容器类型,早期推荐从 typing 导入:
from typing import List, Dict, Tuple, Set, Optional, Union
scores: List[int] = [85, 92, 78]
user: Dict[str, str] = {"id": "001", "name": "Bob"}
point: Tuple[float, float] = (3.5, 7.2)
unique_ids: Set[int] = {1, 2, 3}
maybe_error: Optional[str] = None # str 或 None
data: Union[int, str] = "hello" # int 或 str
从 Python 3.9 开始,可以直接用内置的 list、dict 等作为泛型,不再需要从 typing 导入 List 等:
scores: list[int] = [85, 92, 78]
user: dict[str, str] = {"id": "001", "name": "Bob"}
Python 3.10 又引入了更简洁的联合类型语法:X | Y 代替 Union[X, Y],X | None 代替 Optional[X]:
data: int | str = "hello"
maybe_error: str | None = None
函数类型标注
函数可以标注参数类型和返回值类型(用 -> 标识):
def greet(name: str, times: int = 1) -> str:
return f"Hello, {name}! " * times
当参数可以接受多种类型或 None 时,使用联合类型:
def process(item: int | str) -> str:
return str(item)
对于没有返回值的函数(返回 None),应显式标注 -> None:
def log_message(msg: str) -> None:
print(msg)
接受函数作为参数时,用 Callable:
from typing import Callable
def apply(func: Callable[[int, int], int], x: int, y: int) -> int:
return func(x, y)
如果函数参数或返回值类型不确定,可用 Any(意为“任意类型”,关闭该部分的检查):
from typing import Any
def parse_json(text: str) -> Any:
import json
return json.loads(text)
类的类型标注
类可以用作类型,标注方法与属性。
class User:
name: str
age: int
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
def birthday(self) -> None:
self.age += 1
注意:name: str 写在类体里只是类型提示,并不会真正定义实例属性,还是需要 init 里用 self.name = name 来创建。不过,这个提示对静态检查器和 IDE 很有用。
当方法需要返回当前类的实例时,可以用 -> "ClassName" 的字符串形式(因为类定义尚未完成):
class Node:
def set_next(self, node: "Node") -> None:
...
def clone(self) -> "Node":
...
Python 3.11+ 可以使用 typing.Self 来表示返回当前类(更推荐):
from typing import Self
class Node:
def clone(self) -> Self:
return self.__class__()
泛型类:如果你想写一个能容纳任意类型的容器,可以用 TypeVar 和 Generic:
from typing import TypeVar, Generic
T = TypeVar('T')
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
使用时标注具体类型:
int_stack: Stack[int] = Stack()
常用工具与最佳实践
- 静态类型检查器
- mypy:最成熟,安装后运行
mypy your_script.py即能报告类型错误。 - pyright / pylance:VS Code 内置的语言服务器,提供实时检查反馈。
- 二者都能与预提交钩子 (pre-commit) 和 CI 流程结合,在合并前拦截类型问题。
- 运行时类型无关
类型提示 不会 强制类型检查。以下代码完全合法,不会报错:
x: int = "hello" # 运行时不会报任何错误
要让运行时检查类型,需借助 pydantic、dataclasses 的 field 验证或 typeguard 等库。
- 渐进式标注
不要追求一次性给整个项目加上完整类型。可以:
- 优先标注公有 API 的函数签名。
- 对复杂逻辑或容易出错的部分加上类型。
- 使用
Any作为临时“逃生口”,等理清逻辑后再细化。 - 配置文件
mypy.ini逐步收紧检查等级(从disallow_untyped_defs=False开始)。
- 类型别名
复杂的嵌套类型可以用别名提高可读性:
Vector = list[float]
UserDict = dict[str, str | int | None]
- 处理第三方库无类型提示
- 生成或安装对应的 stub 包(如
requests就有types-requests)。 - 对于极个别无法标注的调用,加
# type: ignore注释来暂时屏蔽检查。
- 与数据类搭配
dataclasses 天生就需要类型提示:
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
这样既定义了字段,又自动生成了 init,同时保留了完整的类型信息,一举多得。
类型提示是 Python 工程化的关键拼图。它让大型项目的代码更可维护、重构更安全,也让你在使用现代 IDE 时享受到精准的自动补全和错误提示。不必等到项目完美时再开始,从下一个函数签名加一个 -> str 开始,你就会体验到它带来的踏实感。