返回类实例的抽象类方法的键入提示

时间:2019-06-16 16:58:24

标签: python python-3.x typing python-typing

我在以下代码上遇到类型检查器错误,我很想了解如何解决该错误。

以下基类有一个抽象类方法,我希望每个从其继承的子类都将实现一个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")

1 个答案:

答案 0 :(得分:0)

错误与返回类型decode中只有一个TypeVar有关。目前尚不清楚究竟是什么意思–您或多或少试图声明MetricBase的每个子类都需要支持返回MetricBase的其他任意子类,这将以某种神奇的方式根据该函数的调用方式进行推断。

这实际上不是用Python可以完成的事情。

您需要做的是以下操作之一:

  1. 放弃并且不使用TypeVars
  2. MetricBase设为通用类,并让您的子类继承MetricBase的参数化版本。
  3. 以某种方式在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>