第 18 章 类型系统与类型提示
18.1 渐进式类型系统理念
Python 的类型系统是渐进式(Gradual) 的:
- 类型注解是可选的,不写也完全合法
- 注解不影响运行时行为——Python 不会因为类型标注错误而拒绝运行
- 真正的类型检查由外部工具(mypy、pyright)完成
# 无类型注解 — 完全合法
def add(a, b):
return a + b
# 有类型注解 — 更清晰
def add(a: int, b: int) -> int:
return a + b
# 类型注解是元数据,运行时可以访问
print(add.__annotations__)
# {'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}
# 但不会强制执行
add("hello", " world") # 运行正常,返回 "hello world"
18.2 基础类型注解
# 变量注解(Python 3.6+)
name: str = "Alice"
age: int = 30
height: float = 1.68
is_active: bool = True
# 函数注解
def greet(name: str, times: int = 1) -> str:
return f"Hello, {name}! " * times
# 容器类型(Python 3.9+ 可直接用内置类型)
names: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {"Alice": 90, "Bob": 85}
coords: tuple[float, float] = (3.0, 4.0)
unique_ids: set[int] = {1, 2, 3}
# 变长元组
values: tuple[int, ...] = (1, 2, 3, 4, 5)
Optional 和 Union
# Python 3.10 之前
from typing import Optional, Union
def find_user(user_id: int) -> Optional[str]:
"""返回用户名,未找到返回 None"""
# Optional[str] 等价于 Union[str, None]
return None
def parse_id(value: Union[str, int]) -> int:
return int(value)
# Python 3.10+ 使用 | 语法
def find_user(user_id: int) -> str | None:
return None
def parse_id(value: str | int) -> int:
return int(value)
常用类型
from typing import Any, NoReturn
from collections.abc import Callable, Iterable, Iterator, Sequence
# Any — 任何类型(等于没写)
def log(message: Any) -> None:
print(message)
# NoReturn — 函数永远不会正常返回
def fatal_error(msg: str) -> NoReturn:
raise SystemExit(msg)
# Callable — 可调用对象
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
# Iterable / Iterator / Sequence
def process(items: Iterable[str]) -> list[str]:
return [item.upper() for item in items]
def first(seq: Sequence[int]) -> int:
return seq[0]
18.3 TypeVar 与泛型 Generic
from typing import TypeVar
T = TypeVar("T")
# 泛型函数 — 输入和输出类型一致
def first(items: list[T]) -> T:
return items[0]
result = first([1, 2, 3]) # 推断 T=int,返回 int
result = first(["a", "b"]) # 推断 T=str,返回 str
# 有约束的 TypeVar
Num = TypeVar("Num", int, float)
def add(a: Num, b: Num) -> Num:
return a + b
# 有上界的 TypeVar
from typing import Hashable
H = TypeVar("H", bound=Hashable)
泛型类
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()
def peek(self) -> T:
return self._items[-1]
def is_empty(self) -> bool:
return len(self._items) == 0
# 使用
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
value: int = int_stack.pop()
str_stack: Stack[str] = Stack()
str_stack.push("hello")
Python 3.12+ 新语法
# 旧语法
T = TypeVar("T")
def first(items: list[T]) -> T: ...
# 新语法(Python 3.12+)
def first[T](items: list[T]) -> T: ...
class Stack[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()
18.4 Protocol 结构化子类型
Protocol 实现了鸭子类型的形式化,无需继承即可满足类型约束:
from typing import Protocol
class HasLength(Protocol):
def __len__(self) -> int: ...
class HasName(Protocol):
name: str
def print_length(obj: HasLength) -> None:
print(f"长度: {len(obj)}")
print_length([1, 2, 3]) # 通过——list 有 __len__
print_length("hello") # 通过——str 有 __len__
print_length({"a": 1}) # 通过——dict 有 __len__
class Dog:
name: str
def __init__(self, name: str):
self.name = name
def greet(obj: HasName) -> str:
return f"Hello, {obj.name}!"
greet(Dog("Buddy")) # 通过——Dog 有 name 属性
复杂 Protocol
from typing import Protocol
class Comparable(Protocol):
def __lt__(self, other: "Comparable") -> bool: ...
def __le__(self, other: "Comparable") -> bool: ...
class Serializable(Protocol):
def to_dict(self) -> dict: ...
@classmethod
def from_dict(cls, data: dict) -> "Serializable": ...
class Repository(Protocol[T]):
def get(self, id: int) -> T | None: ...
def save(self, entity: T) -> None: ...
def delete(self, id: int) -> bool: ...
18.5 Literal、Final、TypedDict
from typing import Literal, Final, TypedDict
# Literal — 限定字面量值
def set_direction(direction: Literal["north", "south", "east", "west"]) -> None:
print(f"方向: {direction}")
set_direction("north") # 通过
# set_direction("up") # mypy 报错
# Final — 不可重新赋值的常量
MAX_SIZE: Final = 100
# MAX_SIZE = 200 # mypy 报错
class Config:
DEBUG: Final[bool] = False
# TypedDict — 带类型的字典
class UserDict(TypedDict):
name: str
age: int
email: str | None
# 可选键
class UserDict(TypedDict, total=False):
name: str # 必选(需要 Required)
age: int # 可选
email: str # 可选
from typing import Required, NotRequired
class UserDict(TypedDict):
name: Required[str]
age: NotRequired[int]
email: NotRequired[str]
user: UserDict = {"name": "Alice"} # 通过
18.6 ParamSpec 与 Concatenate
为装饰器编写精确的类型注解:
from typing import ParamSpec, Callable, TypeVar
from functools import wraps
P = ParamSpec("P")
R = TypeVar("R")
def log_call(func: Callable[P, R]) -> Callable[P, R]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_call
def add(a: int, b: int) -> int:
return a + b
# 类型检查器知道 add 仍然接受 (int, int) -> int
result: int = add(1, 2)
from typing import Concatenate
# 装饰器添加额外参数
def with_user(
func: Callable[Concatenate[str, P], R]
) -> Callable[P, R]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
return func("default_user", *args, **kwargs)
return wrapper
@with_user
def greet(user: str, message: str) -> str:
return f"{user}: {message}"
greet("hello") # 自动注入 user 参数
18.7 TypeGuard 与类型窄化
from typing import TypeGuard
def is_string_list(val: list[object]) -> TypeGuard[list[str]]:
"""类型守卫:检查是否为字符串列表"""
return all(isinstance(x, str) for x in val)
def process(items: list[object]) -> None:
if is_string_list(items):
# 这里 items 的类型被窄化为 list[str]
for item in items:
print(item.upper()) # mypy 知道 item 是 str
# isinstance 自动窄化类型
def handle(value: str | int) -> None:
if isinstance(value, str):
print(value.upper()) # mypy 知道是 str
else:
print(value + 1) # mypy 知道是 int
# assert 也能窄化
def process(x: str | None) -> str:
assert x is not None
return x.upper() # mypy 知道 x 不是 None
18.8 type 语句与类型别名(Python 3.12+)
# 旧方式 — 用赋值创建类型别名
from typing import TypeAlias
Vector: TypeAlias = list[float]
Matrix: TypeAlias = list[list[float]]
UserID: TypeAlias = int | str
# 新方式 — type 语句(Python 3.12+)
type Vector = list[float]
type Matrix = list[list[float]]
type UserID = int | str
# 泛型类型别名
type ListOrSet[T] = list[T] | set[T]
def process(data: ListOrSet[int]) -> int:
return sum(data)
18.9 使用 mypy / pyright 进行静态检查
mypy
pip install mypy
mypy your_script.py
mypy --strict your_script.py # 严格模式
# example.py
def greet(name: str) -> str:
return "Hello, " + name
greet(42) # mypy: Argument 1 to "greet" has incompatible type "int"; expected "str"
pyright
pip install pyright
pyright your_script.py
配置文件
# pyproject.toml
[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
disallow_untyped_defs = true
[tool.pyright]
pythonVersion = "3.12"
typeCheckingMode = "strict"
处理无法标注的情况
from typing import cast, TYPE_CHECKING
# cast — 告诉类型检查器"相信我"
value = cast(int, some_unknown_value)
# type: ignore — 跳过该行检查
result = messy_function() # type: ignore
# TYPE_CHECKING — 只在类型检查时为 True,运行时为 False
if TYPE_CHECKING:
from heavy_module import HeavyClass
def func(obj: "HeavyClass") -> None: # 字符串形式的前向引用
pass
本章小结:Python 的类型系统是渐进式的——你可以逐步为代码添加类型注解。掌握泛型(TypeVar/Generic)、Protocol(结构化子类型)、以及装饰器类型标注(ParamSpec)是高级用法的关键。配合 mypy 或 pyright,类型提示能在运行前捕获大量 bug。