我在以下代码上遇到类型检查器错误,我很想了解如何解决该错误。
以下基类有一个抽象类方法,我希望每个从其继承的子类都将实现一个decode
函数,该函数返回该子类的实例。
from abc import ABC, abstractmethod
from typing import TypeVar
TMetricBase = TypeVar("TMetricBase", bound="MetricBase")
class MetricBase(ABC):
@abstractmethod
def add(self, element: str) -> None:
pass # pragma: no cover
@classmethod
@abstractmethod
def decode(cls, json_str: str) -> TMetricBase:
pass # pragma: no cover
子类如下所示
import json
from typing import Any, Callable, List, Mapping, Optional
from something import MetricBase, TMetricBase
class DiscreteHistogramMetric(MetricBase):
def __init__(self, histogram: Optional[Mapping[str, int]]) -> None:
super().__init__()
self._histogram = dict(histogram) if histogram else {}
def add(self, element: str) -> None:
self._histogram[element] = self._histogram.get(element, 0) + 1
@classmethod
def decode(cls, json_str: str) -> "DiscreteHistogramMetric":
json_obj = json.loads(json_str)
histogram_map = json_obj["DiscreteHistogramMetric"]
return cls(histogram=histogram_map)
我遇到以下错误:
error: Return type of "decode" incompatible with supertype "MetricBase"
将decode
的返回类型更改为TMetricBase
时,出现以下错误:
error: Incompatible return value type (got "DiscreteHistogramMetric", expected "TMetricBase")
答案 0 :(得分:0)
错误与返回类型decode
中只有一个TypeVar有关。目前尚不清楚究竟是什么意思–您或多或少试图声明MetricBase
的每个子类都需要支持返回MetricBase
的其他任意子类,这将以某种神奇的方式根据该函数的调用方式进行推断。
这实际上不是用Python可以完成的事情。
您需要做的是以下操作之一:
MetricBase
设为通用类,并让您的子类继承MetricBase
的参数化版本。TMetricBase
参数中使用decode
。 (通过这种方式,我们实际上可以推断出返回类型应该是什么。)我假设您已经考虑了第一个解决方案并拒绝了它:它将检查我们的程序类型,但也会使decode
方法变得毫无用处/需要笨拙的转换。
第二个解决方案看起来像这样:
from abc import ABC, abstractmethod
from typing import TypeVar, Generic
TMetricBase = TypeVar("TMetricBase", bound="MetricBase")
class MetricBase(ABC, Generic[TMetricBase]):
@classmethod
@abstractmethod
def decode(cls, json_str: str) -> TMetricBase:
pass
class DiscreteHistogramMetric(MetricBase['DiscreteHistogramMetric']):
@classmethod
def decode(cls, json_str: str) -> "DiscreteHistogramMetric":
pass
通过拥有DiscreteHistogramMetric
子类MetricBase[DiscreteHistogramMetric]
而不是直接拥有MetricBase
,我们实际上可以将typevar约束为有意义的东西。
尽管这种解决方案仍然有些笨拙-无论如何MetricBase
都必须子类化,我们才能在使用MetricBase的任何地方开始使用泛型,这很烦人。
表面上的第三个解决方案最初听起来甚至更笨拙:我们要添加一些额外的虚拟第三参数还是废话?事实证明,我们可以使用一个不错的技巧-我们可以使用generic selfs来注释cls
变量!
通常,该变量的类型是推断的,不需要注释,但是在这种情况下,这样做很有帮助:我们可以使用有关cls
的确切信息来帮助生成更多精确的返回类型。
是这样的:
from abc import ABC, abstractmethod
from typing import TypeVar, Type
TMetricBase = TypeVar("TMetricBase", bound="MetricBase")
class MetricBase(ABC):
@classmethod
@abstractmethod
def decode(cls: Type[TMetricBase], json_str: str) -> TMetricBase:
pass
class DiscreteHistogramMetric(MetricBase):
def __init__(self, something: str) -> None:
pass
@classmethod
def decode(cls: Type[TMetricBase], json_str: str) -> TMetricBase:
# Note that we need to use create the class by using `cls` instead of
# using `DiscreteHistogramMetric` directly.
return cls("blah")
不幸的是,我们需要继续在子类中使用TypeVars,而不是像在问题中那样简单地定义它-我相信这种行为是a bug in mypy。
但是,它确实能做到这一点:做DiscreteHistogramMetric.decode("blah")
将返回预期的TMetricBase
。
与第一种方法不同,这种混乱至少可以很好地限制在decode
方法中,并且不需要在使用MetricBase
类的任何地方都开始使用泛型。 / p>