【问题标题】:Typing hint for abstract class method that returns class instance返回类实例的抽象类方法的输入提示
【发布时间】:2019-06-16 16:58:24
【问题描述】:

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

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

【问题讨论】:

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


【解决方案1】:

错误与您在 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,我们实际上可以将类型变量限制为有意义的东西。

这个解决方案仍然有点笨拙——必须继承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 类的任何地方开始使用泛型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-28
    • 1970-01-01
    • 2014-01-11
    • 2010-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-06
    相关资源
    最近更新 更多