Я хочу убедиться, что from_dict
в следующем методе хорошо работает и в его подклассах. В настоящее время его ввод не работает (ошибка mypy "Несовместимый тип возвращаемого значения"). Я думаю, потому что подкласс возвращает экземпляр подкласса, а не экземпляр суперкласса.
from __future__ import annotations
from abc import ABC
from dataclasses import dataclass
from typing import ClassVar, Type
@dataclass
class Statement(ABC):
@classmethod
def from_dict(cls) -> Statement:
return cls()
@dataclass
class Parent(ABC):
SIGNATURE_CLS: ClassVar[Type[Statement]]
def say(self) -> Statement:
# Initialize a Statement through a from_dict classmethod
return self.SIGNATURE_CLS.from_dict()
@dataclass
class ChildStatement(Statement):
pass
@dataclass
class Child(Parent, ABC):
SIGNATURE_CLS = ChildStatement
def say(self) -> ChildStatement:
# Initialize a ChildStatement through a from_dict classmethod
# that ChildStatement inherits from Statement
return self.SIGNATURE_CLS.from_dict()
Приведенный выше код дает эту ошибку MyPy:
Incompatible return value type (got "Statement", expected "ChildStatement") [return-value]
Я думаю, что это вариант использования TypeVar
в Statement
, но я не уверен, как это реализовать и, особенно, в чем смысл этого.
Экземпляр подкласса является экземпляром своего суперкласса в соответствии с правилами типизации. Ошибка, которую вы видите, связана с тем, что тип from_dict
возвращает Statement
, а вы пытаетесь вернуть это значение из say
, которое гарантированно возвращает ChildStatement
. Итак, ваша проблема в том, что вы потенциально можете вернуть более общий Statement
там, где ожидается более конкретный ChildStatement
.
Вам нужно как-то гарантировать MyPy, что:
from_dict
возвращает свой фактический класс, а не общий Statement
.SIGNATURE_CLS
из Child
будет ChildStatement
и, возможно, Statement
(просто присвоить ChildStatement
недостаточно, поскольку вы явно ввели его как ClassVar[Type[Statement]]
Вы можете сделать это с помощью следующих фрагментов кода:
return cls()
from typing import Self
@dataclass
class Statement(ABC):
@classmethod
def from_dict(cls) -> Self:
return cls()
(Важно отметить, что typing.Self
— это очень свежее дополнение, представленное в Python 3.11, вам также может потребоваться обновить MyPy, чтобы учесть это)
Child.SIGNATURE_CLS
@dataclass
class Child(Parent, ABC):
SIGNATURE_CLS : ClassVar[Type[ChildStatement]] = ChildStatement
В качестве последнего замечания, я задаюсь вопросом о чувствительности метода Statement.from_dict()
, поскольку он ничем не отличается от простого использования стандартного конструктора Statement()
.
Я прочитал PEP, и вариант использования, который они описывают с помощью Self
для использования его в методах класса, действительно именно то, что мне нужно!
С «добрым старым» TypeVar это было бы _T = TypeVar('_T', bound='Statement'); class Statement(ABC): @classmethod def from_dict(cls: type[_T]) -> _T: return cls()
Похоже, это будет решено с помощью новой подсказки типа Self (доступно в python 3.11)