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