Python如何写泛型代码_Generic类型设计实践

6次阅读

python泛型依赖type hints与mypy等工具实现静态类型检查,核心是Generic、typevar和protocol;typevar定义类型变量并可加约束或协变性,generic用于参数化类,protocol支持结构化接口,泛型信息运行时被擦除。

Python如何写泛型代码_Generic类型设计实践

Python 的泛型不是编译时强约束,而是靠类型提示(Type Hints)+ 类型检查工具(如 mypy)协同实现的。写泛型代码的核心是用 GenericTypeVarProtocol 表达“类型参数化”的意图,让函数或类能安全地处理多种类型,同时保留类型信息供 ide 提示和静态检查。

用 TypeVar 定义可复用的类型变量

TypeVar 是泛型的起点,它声明一个占位符类型,后续可在函数签名、类定义中反复引用。关键点是:类型变量需显式绑定约束或协变性,否则默认是 invariant(不变型),容易导致误报。

  • 基础用法:T = TypeVar('T') 表示任意类型;def first(lst: List[T]) -> T: 能推断返回值与列表元素同类型
  • 加约束更安全:number = TypeVar('Number', int, Float),限制 T 只能是 int 或 float,避免传入 str 导致运行时错误
  • 协变/逆变需明确:class Container(Generic[T_co], covariant=True)(需继承 Generic 并设 boundcovariant 参数)

在类中使用 Generic 实现参数化容器

自定义泛型类必须继承 Generic[...],并在类名后标注类型参数。注意:运行时不生效,仅用于类型检查和 IDE 支持。

  • 正确写法:class Stack(Generic[T]): def push(self, item: T) -> None: ... def pop(self) -> T: ...
  • 不能只写 class Stack[T]:(这是 Python 3.12+ 的简写语法,但需确保环境支持且 mypy 版本兼容)
  • 若类有多个类型参数,如 class Pair(Generic[K, V]):,需一一对应使用,不可省略或错序

用 Protocol 替代继承,实现结构化泛型接口

当不想强制用户继承某个基类,又希望约束“具备某些方法”,就用 Protocol。它是鸭子类型 + 类型安全的结合,比抽象基类(ABC)更轻量、更灵活。

立即学习Python免费学习笔记(深入)”;

  • 定义协议:class Drawable(Protocol): def draw(self) -> None: ...
  • 泛型函数接受协议:def render_all(items: Sequence[Drawable]) -> None:,任何有 draw() 方法的对象都可通过检查
  • 可与 TypeVar 结合:T_draw = TypeVar('T_draw', bound=Drawable),进一步限定泛型参数必须满足协议

避免常见陷阱:运行时擦除与检查时机

Python 泛型在运行时被完全擦除(type parameter 消失),所以 isinstance(x, Stack[int]) 会报错。所有类型逻辑只在开发阶段起作用。

  • 不要试图在运行时获取泛型参数:Stack[int].__args__ 在 3.8+ 可读,但不保证稳定,也不该用于逻辑分支
  • 单元测试仍需覆盖真实数据类型,类型提示不能替代测试
  • mypy 默认不检查未标注函数,记得对关键模块启用 --disallow-untyped-defs
text=ZqhQzanResources