Ошибка связана с тем, что у вас есть только одна TypeVar в возвращаемом типе decode
. Неясно, что именно это будет означать - вы более или менее пытаетесь объявить, что каждый отдельный подкласс MetricBase
должен поддерживать возврат любого другого произвольного подкласса MetricBase
, который он каким-то волшебным образом выведет на основе того, как эта функция выполняется. называется.
На самом деле это не то, что можно сделать в Python.
Вместо этого вам нужно сделать одно из следующего:
- Откажитесь и не используйте TypeVars
- Сделайте
MetricBase
общим классом, а ваши подклассы наследуют параметризованную версию MetricBase
.
- Каким-то образом используйте
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
напрямую, мы можем фактически ограничить переменную типа чем-то значимым.
Это решение по-прежнему немного неуклюже — создание подкласса MetricBase
требует, чтобы мы начали использовать дженерики везде, где мы используем MetricBase, что довольно раздражает.
Третье решение на первый взгляд звучит еще более неуклюже: мы собираемся добавить какой-то дополнительный фиктивный третий параметр или какую-то ерунду? Но оказывается, есть хороший трюк, который мы можем использовать — мы можем использовать общие селфи, чтобы аннотировать переменную 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
.
person
Michael0x2a
schedule
16.06.2019