Подсказка для метода абстрактного класса, который возвращает экземпляр класса - PullRequest
2 голосов
/ 16 июня 2019

В следующем коде я получаю сообщения о проверке типов, хотелось бы понять, как устранить ошибку.

В следующем базовом классе есть метод абстрактного класса, я хочу, чтобы каждый дочерний класс, который наследовал от него, реализовал функцию 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"

При изменении типа возврата decodeTMetricBase, я получаю следующую ошибку:

error: Incompatible return value type (got "DiscreteHistogramMetric", expected "TMetricBase")

1 Ответ

0 голосов
/ 16 июня 2019

Ошибка связана с тем, что у вас есть только один TypeVar в типе возврата decode. Непонятно, что это будет означать, точно - вы более или менее пытаетесь объявить, что каждый отдельный подкласс 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 внутри подкласса, а не определять его более просто, как вы это делали в своем вопросе - я считаю, что это поведение ошибка в mypy .

Однако, делает , добивается цели: выполнение DiscreteHistogramMetric.decode("blah") вернет TMetricBase, как и ожидалось.

И в отличие от первого подхода, беспорядок, по крайней мере, довольно хорошо ограничен методом decode и не требует, чтобы вы начали использовать дженерики везде, где вы также используете MetricBase классы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...