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

Я хочу убедиться, что 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, но я не уверен, как это реализовать и, особенно, в чем смысл этого.

Похоже, это будет решено с помощью новой подсказки типа Self (доступно в python 3.11)

Florent Monin 30.03.2023 13:58
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
1
87
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Экземпляр подкласса является экземпляром своего суперкласса в соответствии с правилами типизации. Ошибка, которую вы видите, связана с тем, что тип from_dict возвращает Statement, а вы пытаетесь вернуть это значение из say, которое гарантированно возвращает ChildStatement. Итак, ваша проблема в том, что вы потенциально можете вернуть более общий Statement там, где ожидается более конкретный ChildStatement.

Вам нужно как-то гарантировать MyPy, что:

  1. from_dict возвращает свой фактический класс, а не общий Statement.
  2. SIGNATURE_CLS из Child будет ChildStatement и, возможно, Statement (просто присвоить ChildStatement недостаточно, поскольку вы явно ввели его как ClassVar[Type[Statement]]

Вы можете сделать это с помощью следующих фрагментов кода:

  1. используйте typing.Self, чтобы отразить return cls()
from typing import Self

@dataclass
class Statement(ABC):
    @classmethod
    def from_dict(cls) -> Self:
        return cls()

(Важно отметить, что typing.Self — это очень свежее дополнение, представленное в Python 3.11, вам также может потребоваться обновить MyPy, чтобы учесть это)

  1. Явно введите Child.SIGNATURE_CLS
@dataclass
class Child(Parent, ABC):
    SIGNATURE_CLS : ClassVar[Type[ChildStatement]] = ChildStatement

В качестве последнего замечания, я задаюсь вопросом о чувствительности метода Statement.from_dict(), поскольку он ничем не отличается от простого использования стандартного конструктора Statement().

Я прочитал PEP, и вариант использования, который они описывают с помощью Self для использования его в методах класса, действительно именно то, что мне нужно!

Bram Vanroy 30.03.2023 14:05

С «добрым старым» TypeVar это было бы _T = TypeVar('_T', bound='Statement'); class Statement(ABC): @classmethod def from_dict(cls: type[_T]) -> _T: return cls()

SUTerliakov 30.03.2023 16:28

Другие вопросы по теме