
mypy 不报错,是因为子类中 @overload 声明的 update 方法参数类型(Union[str, list[str]])在类型系统中是父类抽象方法参数类型(str)的超类型;而函数参数具有逆变性(contravariance),这恰好满足 LSP 要求。
mypy 不报错,是因为子类中 `@overload` 声明的 `update` 方法参数类型(`union[str, list[str]]`)在类型系统中是父类抽象方法参数类型(`str`)的**超类型**;而函数参数具有**逆变性(contravariance)**,这恰好满足 lsp 要求。
在 Python 类型系统中,方法签名的兼容性并非简单地要求“子类参数类型必须与父类完全一致”,而是遵循协变返回类型 + 逆变参数类型的规则。这一设计直接源于函数类型的子类型关系定义:Callable[[A], R] 是 Callable[[B], S] 的子类型,当且仅当 B ≤ A(参数类型逆变)且 R ≤ S(返回类型协变)。
以你的示例为例:
from typing import TypeVar, Union, overload, Any, List, AbstractSet, Generic, Protocol
from abc import ABC, abstractmethod
E = TypeVar("E")
class AbstractRepository(ABC, Generic[E]):
@abstractmethod
async def update(self, entity: E) -> bool:
...
class IndexRepository(AbstractRepository[str]):
@overload
async def update(self, index: str) -> bool: ...
@overload
async def update(self, index: List[str]) -> bool: ...
async def update(self, index: Union[str, List[str]]) -> bool:
# 实际实现:可安全处理 str 或 list[str]
if isinstance(index, str):
print(f"Updating single index: {index}")
else:
print(f"Updating {len(index)} indices")
return True虽然 IndexRepository 继承自 AbstractRepository[str],其抽象契约要求 update 接收 str 类型参数,但子类实际提供了更宽泛的接受能力(str | list[str])。这不违反 LSP,原因如下:
- ✅ LSP 核心要求:任何使用
AbstractRepository[str]的代码,传入str时,必须能在IndexRepository实例上正确运行。 - ✅ 逆变保障:
Union[str, list[str]]是str的超类型(即str ≤ Union[str, list[str]]),因此IndexRepository.update可接受的输入集合 ⊇AbstractRepository[str].update所承诺的输入集合。调用方只传str,子类必然能处理——这是安全的扩展。 - ❌ 反之才危险:若子类将参数收紧为
Literal["users"],则原可传"posts"的调用会失败,这才真正违反 LSP。
⚠️ 注意事项:
立即学习“Python免费学习笔记(深入)”;
-
@overload本身只是编译期类型声明,不改变运行时行为;所有@overload装饰的存根必须由一个非装饰的、具名实现函数覆盖(如你写的async def update(self, index: ...)),否则 mypy 会报错Missing implementation for overloaded function。 - mypy 默认对
@overload使用严格的“重载解析一致性”检查,但不会因参数范围扩大而报错——因为这符合类型安全逻辑。 - 若你希望强制子类严格遵守父类签名(例如用于 API 合约约束),应避免在子类中扩展参数类型;或改用协议(
Protocol)+ 显式类型别名进行更精细的接口建模。
✅ 总结:这不是 mypy 的疏漏,而是类型理论的正确体现。@overload 配合逆变参数,让子类能安全地拓宽接口能力,既保持多态可用性,又提升实现灵活性——这正是 Python 类型系统兼顾表达力与安全性的关键设计。


















