一、为什么需要泛型?
在Python中,泛型的核心价值在于:在"不绑定具体类型"的同时,保留类型关联信息。
典型场景对比:
-
为每种类型单独定义函数 :如
return_int(x: int) -> int、return_str(x: str) -> str------代码重复,无法适配新类型 -
使用
Any类型 :return_any(x: Any) -> Any------虽能适配所有类型,但丢失了输入与输出类型一致的约束,静态检查工具无法捕获类型错误 -
使用泛型 :用类型变量
T占位,既保留代码复用性,又维持类型一致性
泛型的关键特性:参数化类型、类型安全(通过静态检查工具在开发阶段捕获错误)、代码复用、动态兼容(仅作用于静态类型提示,不影响运行时)。
二、核心工具
| 工具 | 作用 |
|---|---|
TypeVar |
定义类型变量,代表"任意类型"或"特定范围的类型",是泛型的"参数" |
Generic |
泛型基类,用于定义泛型类,需结合类型变量使用 |
| 预定义泛型类型 | 如 List[T]、Dict[K, V](Python 3.9+ 可直接用 list[T]、dict[K, V]) |
三、泛型函数
3.1 基础用法(Python 3.12 之前)
通过 TypeVar 定义类型变量,在函数的参数、返回值中使用该变量,实现"输入类型与输出类型一致"的约束。
python
from typing import TypeVar, List
T = TypeVar('T') # 定义无约束类型变量
def get_first(items: List[T]) -> T:
"""返回列表的第一个元素,类型与列表元素类型一致"""
return items[0]
# 使用示例
n: int = get_first([1, 2, 3]) # T 被推断为 int
s: str = get_first(["a", "b"]) # T 被推断为 str
# x: str = get_first([1, 2, 3]) # 类型检查错误:期望返回 int,但标记为 str
3.2 Python 3.12+ 新语法(PEP 695)
从 Python 3.12 开始(PEP 695),可以使用更简洁的方括号语法直接声明泛型函数:
python
from collections.abc import Sequence
def first[T](l: Sequence[T]) -> T: # 直接在函数名后声明类型参数
return l[0]
两种语法完全等价。
3.3 约束类型变量的范围
若函数逻辑仅适用于特定类型,可通过 bound 参数或类型列表限制类型范围:
python
from typing import TypeVar, SupportsAbs
# 方式一:bound - T 必须是 Number 或其子类(如 int, float)
T_Numeric = TypeVar("T_Numeric", bound=SupportsAbs[float])
# 方式二:类型列表 - T 必须是 'str' 或 'int' 之一[reference:15]
T_Exact = TypeVar("T_Exact", str, int)
def concat(x: T_Exact, y: T_Exact) -> T_Exact:
return x + y
bound 只能指定一个类型;如需多种不相关的类型,应使用类型列表。
四、泛型类
4.1 基础用法(Python 3.12 之前)
自定义泛型类需要继承 Generic[T]:
python
from typing import TypeVar, Generic, List
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]()
int_stack.push(1)
# int_stack.push("a") # 类型检查错误
str_stack = Stack[str]()
str_stack.push("hello")
4.2 多个类型变量
泛型类可以有任意数量的类型变量:
python
from typing import TypeVar, Generic
K = TypeVar('K')
V = TypeVar('V')
class KeyValuePair(Generic[K, V]):
def __init__(self, key: K, value: V):
self.key = key
self.value = value
pair = KeyValuePair[str, int]("age", 25)
4.3 Python 3.12+ 新语法
python
class Stack[T]: # 直接声明类型参数,无需继承 Generic
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()
五、高级技巧
5.1 类型别名(TypeAlias)
当类型签名非常复杂时,使用类型别名可大幅提高可读性:
python
from typing import TypeAlias, Dict, List, Union
# Python 3.10+ 推荐方式
ComplexData: TypeAlias = Dict[str, List[Union[int, str]]]
def process_data(data: ComplexData) -> None:
...
5.2 泛型与 Protocol 结合
用 Protocol 抽象容器接口,让依赖更稳定、更可测试:
python
from typing import Protocol, TypeVar
T = TypeVar('T')
class Container(Protocol[T]):
def get(self) -> T: ...
def add(self, item: T) -> None: ...
5.3 协变与逆变(PEP 484)
类型变量可以是协变或逆变的:
python
from typing import TypeVar
T_co = TypeVar('T_co', covariant=True) # 协变
T_contra = TypeVar('T_contra', contravariant=True) # 逆变
5.4 ParamSpec(用于装饰器)
ParamSpec 对于编写保留参数类型信息的装饰器非常有用:
python
from typing import TypeVar, ParamSpec, Callable
P = ParamSpec('P')
R = TypeVar('R')
def log_call(func: Callable[P, R]) -> Callable[P, R]:
...
5.5 TypeVarTuple(用于可变长度泛型)
TypeVarTuple 支持处理可变数量的类型参数。
六、常见错误与避坑指南
| 错误 | 正确做法 |
|---|---|
忘记导入 TypeVar 或 Generic |
from typing import TypeVar, Generic |
泛型类未继承 Generic[T] |
class MyContainer(Generic[T]): |
在运行时检查泛型类型参数(如 isinstance(data[0], T)) |
泛型信息在运行时会被擦除,不能用于 isinstance |
| 在泛型函数体内部使用未绑定的类型变量 | 类型变量仅用于函数签名,不在函数体中使用 |
| 嵌套泛型类使用相同的类型变量名 | 不同作用域的类型变量应使用不同名称 |
Python 3.8 及更早版本使用 list[int] 语法 |
使用 from __future__ import annotations 或从 typing 导入 List |
七、最佳实践总结
-
新代码优先使用 Python 3.12+ 的 PEP 695 语法 (
def func[T]、class Stack[T]),更简洁明确 -
类型变量命名要有意义 :如
T(Type)、K(Key)、V(Value) -
优先使用抽象集合类型 (如
Sequence、Iterable)而非具体类型(如list)作为参数类型 -
使用
bound约束类型范围,而非在函数内部做类型检查 -
Python 3.9+ 可直接使用内置类型作为泛型 (
list[int]、dict[str, int]),无需从typing导入List、Dict -
配合静态类型检查工具(如 mypy、Pyright)在开发阶段捕获类型错误
-
类型提示主要用于静态分析,Python 运行时不会强制类型检查