Mypy无法从带绑定的TypeVar正确推导类型,仅识别父类而非所有子类
Mypy无法从带绑定的TypeVar正确推导类型,仅识别父类而非所有子类
我之前也碰到过这个头疼的问题,本质是mypy对带绑定约束的TypeVar在模式匹配时的类型推导存在局限性——它只会把SomeContext视为Context的抽象子类型,不会自动枚举所有Context的具体子类来完成穷尽性检查,所以最后那个case _会被匹配到SomeContext类型,而非预期的Never,触发了类型错误。
先还原你的问题代码,方便大家理解:
from typing import Generic, NoReturn, TypeVar from pydantic import BaseModel from abc import ABC def assert_exhaustiveness(x: NoReturn) -> NoReturn: """Raise an exception to indicate that a case is not handled.""" raise AssertionError("Exhaustiveness check failed, this case should be handled.") class Context(BaseModel, ABC): """Base class for contexts.""" class DashboardContext(Context): """Context for a dashboard.""" class DefaultContext(Context): """Default context for queries without a dashboard.""" SomeContext = TypeVar("SomeContext", bound=Context) class ContextualizedAction(Generic[SomeContext]): """Contextualized Action.""" def __init__(self, context: SomeContext) -> None: self.context = context def apply_action(self) -> str: """Apply the action in the context.""" match self.context: case DashboardContext(): return f"Applying action in {type(self.context)}" case DefaultContext(): return f"Applying action in {type(self.context)}" case _: assert_exhaustiveness(self.context)
解决思路
针对这个问题,有几种实用的解决方案,你可以根据自己的需求选择:
方案1:明确缩小类型范围
在apply_action方法里,给self.context添加一个具体的联合类型注解,直接告诉mypy它只能是DashboardContext或DefaultContext,这样模式匹配的穷尽性检查就能正常工作:
def apply_action(self) -> str: """Apply the action in the context.""" # 明确指定变量的类型为两个子类的联合 context: DashboardContext | DefaultContext = self.context match context: case DashboardContext(): return f"Applying action in {type(context)}" case DefaultContext(): return f"Applying action in {type(context)}" case _: assert_exhaustiveness(context)
方案2:修改TypeVar的约束为具体子类联合
如果你的Context子类范围固定,不需要动态扩展,也可以直接把TypeVar的约束改成具体子类的联合,这样mypy就能直接推导到所有可能的类型:
# 把绑定改成具体子类的联合,而非抽象父类 SomeContext = TypeVar("SomeContext", DashboardContext, DefaultContext) class ContextualizedAction(Generic[SomeContext]): # 后续代码不变...
方案3:使用类型断言缩小范围
如果需要保留Context的抽象绑定,也可以用typing.cast来强制缩小类型范围(注意:这种方式需要你自己保证代码的正确性,避免遗漏子类):
from typing import cast def apply_action(self) -> str: """Apply the action in the context.""" context = cast(DashboardContext | DefaultContext, self.context) match context: case DashboardContext(): return f"Applying action in {type(context)}" case DefaultContext(): return f"Applying action in {type(context)}" case _: assert_exhaustiveness(context)
另外你提到去掉BaseModel也没用,这很正常——因为问题根源在mypy对带绑定TypeVar的推导逻辑,和Pydantic的BaseModel无关,就算去掉它,mypy的行为也不会改变。
内容来源于stack exchange
相关产品推荐
相关产品推荐

