
本文讲解如何解决 Pydantic 模型继承时因 list 类型不变性(invariance)导致的 mypy 类型检查失败问题,核心方案是改用协变的 typing.Sequence 替代 list。
本文讲解如何解决 pydantic 模型继承时因 `list` 类型不变性(invariance)导致的 mypy 类型检查失败问题,核心方案是改用协变的 `typing.sequence` 替代 `list`。
在 Pydantic 与静态类型检查器(如 mypy)协同工作时,模型字段的类型继承需严格遵循 Python 类型系统的协变(covariance)规则。关键在于:list[T] 是不变的(invariant),即 list[EnglishName] 并不被视为 list[Name] 的子类型,即使 EnglishName 继承自 Name。这导致在子类中直接覆盖父类 list[Name] 字段为 list[EnglishName] 时,mypy 报出 Incompatible types in assignment 错误。
而单个对象字段(如 name: Name → name: EnglishName)能通过类型检查,是因为 EnglishName 确实是 Name 的子类型——这种一对一的继承关系天然满足协变要求。但容器类型的行为不同:list 被设计为可读可写(支持 append, __setitem__ 等操作),因此必须保持不变性以保障类型安全。例如,若允许 list[EnglishName] 赋值给 list[Name],就可能意外向其中插入非 EnglishName 的 Name 实例,破坏子类契约。
✅ 正确解法:使用协变的只读序列协议类型 —— typing.Sequence:
from pydantic import BaseModel
from typing import Sequence
class Name(BaseModel):
data: str
class EnglishName(Name):
# 可选:添加特有字段或逻辑
pass
class Animal(BaseModel):
name: Sequence[Name] # ← 使用 Sequence 而非 list
class AmericaAnimal(Animal):
name: Sequence[EnglishName] # ✅ mypy 通过:Sequence 是协变的
? Sequence[T] 表示一个支持索引、长度和迭代的只读序列(如 tuple, list, deque),其泛型参数 T 是协变的:若 EnglishName 是 Name 的子类,则 Sequence[EnglishName] 是 Sequence[Name] 的子类型。这既满足类型安全,又兼容 Pydantic 的运行时验证(Pydantic 会自动将传入的 list[EnglishName] 解析为合法值)。
⚠️ 注意事项:
- 不要使用 typing.List(已弃用)或 list 作为字段类型进行继承覆盖;
- 若业务逻辑确实需要可变列表操作(如 .append()),则不应在子类中窄化类型,而应保留 list[Name],并在运行时通过自定义校验器或 @field_validator 确保元素均为 EnglishName 实例;
- Pydantic v2 中推荐优先使用 list 作为运行时类型(因其语义清晰),但仅在无需继承覆盖类型时使用;涉及继承场景,请统一采用 Sequence 声明接口契约。
总结:类型安全 ≠ 语法便利。理解 invariance 与 covariance 是写出健壮类型化 Pydantic 模型的关键。用 Sequence[T] 替代 list[T],既尊重类型系统规则,又保持代码表达力与可维护性。










