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

14

我在下面的代码中得到了类型检查器错误,我希望能够理解如何解决这个错误。

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

可能是一个副本,与如何注释classmethod返回该类的实例有关? - Aran-Fey
2个回答

14

错误与您在decode的返回类型中仅使用了单个TypeVar有关。这意味着什么并不清楚--您或多或少地试图声明每个MetricBase子类都需要支持返回任何其他任意的MetricBase子类,它将根据调用该函数的方式进行某种神奇推断。

在Python中无法做到这一点。

相反,您需要执行以下操作之一:

  1. 放弃并不使用TypeVars
  2. 使MetricBase成为泛型类,并使您的子类继承MetricBase的参数化版本。
  3. 以某种方式在decode参数中使用TMetricBase。(这样,我们实际上可以推断出返回类型应该是什么)。

我假设您已经考虑过第一个解决方案并拒绝了它:这将使我们的程序进行类型检查,但也会使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时开始使用泛型,这相当烦人。

表面上看,第三种解决方案甚至更加笨拙:我们是否要添加一些额外的虚拟第三个参数或一些无聊的东西?但事实证明,我们可以使用一个很好的技巧--我们可以使用泛型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而不是像你在问题中所做的那样更简单地定义它 - 我认为这种行为是mypy中一个bug

然而,它确实起到了作用:执行DiscreteHistogramMetric.decode("blah")将返回预期的TMetricBase

与第一种方法不同的是,混乱至少相当局限于decode方法,并且不要求您在也使用MetricBase类时开始使用泛型。


6

注意: 这在MyPy 1.0及以上版本中可用

PEP 673引入了“Self类型”,应该在Python 3.11中可用,并可通过typing-extensions包进行向后移植。

from abc import ABC, abstractmethod
from typing import Self


class MetricBase(ABC):
    @classmethod
    @abstractmethod
    def decode(cls, json_str: str) -> Self:
        pass


class DiscreteHistogramMetric(MetricBase):
    def __init__(self, something: str) -> None:
        pass

    @classmethod
    def decode(cls, json_str: str) -> Self:
        return cls("blah")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接