Python typing:重写方法返回派生类如何通过mypy类型检查?
解决抽象基类方法返回子类列表的类型检查问题
你定义了一个抽象基类(ABC),其中的方法声明返回list[Base],但子类实现时返回list[Derived](Derived是Base的子类)会被mypy判定为返回类型不兼容,同时返回list[str]的错误实现也触发同样的错误提示——这不符合预期:前者应该通过检查,后者应该报错。
原问题代码
from abc import ABC, abstractmethod from typing import override class Base: pass class Derived(Base): pass class Abstract(ABC): @abstractmethod def foo(self) -> list[Base]: ... # 预期应该通过检查:返回Derived实例列表(Derived是Base子类) class ConcreteShouldPass(Abstract): @override def foo(self) -> list[Derived]: return [Derived()] # 预期应该触发错误:返回str列表,与Base无关 class ConcreteShouldFail(Abstract): @override def foo(self) -> list[str]: return ["foo"]
触发的mypy错误
foo.py:20: error: Return type "list[Derived]" of "foo" incompatible with return type "list[Base]" in supertype "Abstract" [override] foo.py:26: error: Return type "list[str]" of "foo" incompatible with return type "list[Base]" in supertype "Abstract" [override]
问题原因
Python的list泛型是不变的(invariant),也就是说list[Derived]并不是list[Base]的子类型——虽然Derived是Base的子类,但泛型容器的不变性导致这种替换不被允许:你可以向list[Base]中添加任意Base子类实例,但list[Derived]只能接受Derived实例,类型系统不允许这种潜在的类型不安全场景。
解决方案
方案一:使用协变的泛型容器
如果你的场景可以接受返回更宽泛的序列类型(比如只读序列),可以把抽象方法的返回类型改为Sequence[Base]。Sequence是协变的,意味着Sequence[Derived]是Sequence[Base]的子类型,符合类型安全要求:
from abc import ABC, abstractmethod from typing import override, Sequence class Base: pass class Derived(Base): pass class Abstract(ABC): @abstractmethod def foo(self) -> Sequence[Base]: ... class ConcreteShouldPass(Abstract): @override def foo(self) -> Sequence[Derived]: return [Derived()] # 列表是Sequence的子类,符合要求 class ConcreteShouldFail(Abstract): @override def foo(self) -> Sequence[str]: return ["foo"] # mypy会正确触发错误
方案二:用协变TypeVar定义抽象类
如果你必须返回list类型,可以通过定义协变的TypeVar来实现类型兼容:
from abc import ABC, abstractmethod from typing import override, TypeVar, Generic # 定义协变TypeVar,绑定到Base及其子类 T = TypeVar('T', bound='Base', covariant=True) class Base: pass class Derived(Base): pass class Abstract(ABC, Generic[T]): # 让抽象类继承Generic[T] @abstractmethod def foo(self) -> list[T]: ... class ConcreteShouldPass(Abstract[Derived]): @override def foo(self) -> list[Derived]: return [Derived()] # 类型匹配,通过检查 class ConcreteShouldFail(Abstract[Base]): @override def foo(self) -> list[str]: return ["foo"] # mypy会正确触发错误
这样修改后,ConcreteShouldPass的实现会通过mypy检查,而ConcreteShouldFail的错误实现依然会被捕获。
内容的提问来源于stack exchange,提问作者Øyvind Evju
相关产品推荐
相关产品推荐

