使用ABC强制子类方法命名时解决mypy超类型签名不兼容问题
可行解决方案
下面提供三种不同改造成本的方案,都可以满足你统一方法命名、保留子类参数类型校验、消除mypy报错的需求:
方案1:最小改动,跳过签名校验
如果不需要修改现有代码结构,只要给父类的抽象score方法添加@no_type_check装饰器,或者在子类重写的score方法上添加忽略校验的注释即可:
from abc import ABC, abstractmethod from typing import no_type_check class BaseScorecard(ABC): @abstractmethod @no_type_check def score(self): """ abstractmethod to enforce consistent naming to enforce consistent naming for sanity and automatic doc-building """ raise NotImplementedError def do_common_thing(self): """Some complicated common logic that having a parent class helps with""" ...
这个方案改造成本为0,完全不影响现有业务逻辑和自动文档生成机制。
方案2:入参结构化,符合类型规范
将每个评分卡的入参封装为对应的数据类,父类用泛型定义score方法的入参,既满足Liskov替换原则,又能保留严格的类型校验:
from abc import ABC, abstractmethod from dataclasses import dataclass from typing import Generic, TypeVar ScoreInputT = TypeVar("ScoreInputT") class BaseScorecard(ABC, Generic[ScoreInputT]): @abstractmethod def score(self, input: ScoreInputT) -> int: raise NotImplementedError def do_common_thing(self): ... # 每个评分卡定义专属入参结构 @dataclass class Scorecard1Input: var1: int var2: int var3: str class Scorecard1(BaseScorecard[Scorecard1Input]): def score(self, input: Scorecard1Input) -> int: # 直接通过input.varX访问参数,类型完全可校验 ... @dataclass class Scorecard2Input: var1: str var2: float class Scorecard2(BaseScorecard[Scorecard2Input]): def score(self, input: Scorecard2Input) -> int: ...
这个方案额外获得了入参结构化的好处,参数传递、校验、扩展都更方便,完全符合Python类型规范,没有任何mypy报错。
方案3:分离公共逻辑与命名约束
用Protocol做命名约束,公共逻辑单独由父类提供,完全避免重写签名的校验问题:
from abc import ABC from typing import Protocol # 仅用来约束实现类必须有score方法 class ScorecardProtocol(Protocol): def score(self) -> int: ... # 公共逻辑父类 class BaseScorecard(ABC): def do_common_thing(self): ... class Scorecard1(BaseScorecard): def score(self, var1: int, var2: int, var3: str) -> int: ... class Scorecard2(BaseScorecard): def score(self, var1: str, var2: float) -> int: ...
如果需要校验所有评分卡实现符合规范,只要用mypy检查类是否满足ScorecardProtocol即可,也不会影响现有自动文档生成逻辑。
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

