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

Ice*_*ube 8 python type-hinting python-3.x 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


Run Code Online (Sandbox Code Playgroud)

子类如下所示

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)

Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

error: Return type of "decode" incompatible with supertype "MetricBase"
Run Code Online (Sandbox Code Playgroud)

decode的返回类型更改为 时TMetricBase,出现以下错误:

error: Incompatible return value type (got "DiscreteHistogramMetric", expected "TMetricBase")
Run Code Online (Sandbox Code Playgroud)

Mic*_*x2a 6

该错误与您如何在decode. 目前还不清楚这究竟意味着什么——您或多或少地试图声明 的每个子类都MetricBase需要支持返回 的任何其他任意子类MetricBase,它会以某种方式根据该函数的调用方式神奇地推断出。

这在 Python 中是不可能做到的。

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

  1. 放弃并且不使用 TypeVars
  2. 创建MetricBase一个泛型类并让您的子类继承MetricBase.
  3. 以某种方式TMetricBasedecode参数中使用。(这样,我们实际上可以推断出返回类型应该是什么)。

我假设您已经考虑了第一个解决方案并拒绝了它:它会使我们的程序类型检查,但也会使该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
Run Code Online (Sandbox Code Playgroud)

通过使用DiscreteHistogramMetric子类MetricBase[DiscreteHistogramMetric]而不是MetricBase直接使用,我们实际上可以将类型变量限制为有意义的东西。

不过,这个解决方案仍然有点笨拙——必须子类化MetricBase要求我们在使用 MetricBase 的地方开始使用泛型,这很烦人。

表面上的第三个解决方案最初听起来更笨拙:我们是要添加一些额外的虚拟第三个参数还是一些废话?但事实证明,我们可以使用一个很好的技巧——我们可以使用通用 self来注释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")
Run Code Online (Sandbox Code Playgroud)

有点不幸的是,我们需要继续在子类中使用 TypeVars 而不是像您在问题中所做的那样更简单地定义它——我相信这种行为是mypy 中的一个错误

但是,它确实可以解决问题:执行DiscreteHistogramMetric.decode("blah")将按TMetricBase预期返回 a 。

与第一种方法不同的是,混乱至少很好地局限于decode方法,并且不需要您在使用MetricBase类的任何地方开始使用泛型。


Bła*_*lik 6

注意:这在 MyPy 1.0 及更高版本中可用

PEP 673引入了“ SelfType”,它应该在 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")
Run Code Online (Sandbox Code Playgroud)