Python подсказка переопределения типа при возврате метода в дочерний класс, без переопределения сигнатуры метода - PullRequest
1 голос
/ 10 апреля 2020

У меня есть базовый класс с подсказкой типа float для возврата метода.

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


Пример кода

#!/usr/bin/env python3.6


class SomeClass:
    """This class's some_method will return float."""

    RET_TYPE = float

    def some_method(self, some_input: str) -> float:
        return self.RET_TYPE(some_input)


class SomeChildClass(SomeClass):
    """This class's some_method will return int."""

    RET_TYPE = int


if __name__ == "__main__":
    ret: int = SomeChildClass().some_method("42"). # 
    ret2: float = SomeChildClass().some_method("42")

Моя IDE жалуется на несоответствие типов:

pycharm expected type float

Это происходит потому, что моя IDE все еще использует подсказку типа из SomeClass.some_method.


Исследования

Я думаю, решение может заключаться в использовании обобщений, но я не уверен, есть ли более простой способ.

Python: как переопределить подсказку типа для атрибута экземпляра в подклассе ?

Предлагает, возможно, использовать аннотации переменных экземпляра , но я не уверен, как это сделать для возвращаемого типа.

Ответы [ 3 ]

1 голос
/ 14 апреля 2020

Следующий код прекрасно работает на PyCharm. Я добавил кейс complex, чтобы сделать его более понятным.

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

from typing import ClassVar, Generic, TypeVar, Callable


S = TypeVar('S', bound=complex)


class SomeMethodImplementor(Generic[S]):
    RET_TYPE: ClassVar[Callable]

    def some_method(self, some_input: str) -> S:
        return self.__class__.RET_TYPE(some_input)


class SomeClass(SomeMethodImplementor[complex]):
    RET_TYPE = complex


class SomeChildClass(SomeClass, SomeMethodImplementor[float]):
    RET_TYPE = float


class OtherChildClass(SomeChildClass, SomeMethodImplementor[int]):
    RET_TYPE = int


if __name__ == "__main__":
    ret: complex = SomeClass().some_method("42")
    ret2: float = SomeChildClass().some_method("42")
    ret3: int = OtherChildClass().some_method("42")
    print(ret, type(ret), ret2, type(ret2), ret3, type(ret3))

Если вы измените, например, с ret2: float на ret2: int, будет правильно отображена ошибка типа.

К сожалению, mypy отображает ошибки в этом случае (версия 0.770),

otherhint.py:20: error: Incompatible types in assignment (expression has type "Type[float]", base class "SomeClass" defined the type as "Type[complex]")
otherhint.py:24: error: Incompatible types in assignment (expression has type "Type[int]", base class "SomeClass" defined the type as "Type[complex]")
otherhint.py:29: error: Incompatible types in assignment (expression has type "complex", variable has type "float")
otherhint.py:30: error: Incompatible types in assignment (expression has type "complex", variable has type "int")

Первые ошибки могут быть "исправлены" написанием

    RET_TYPE: ClassVar[Callable] = int

для каждого подкласса. Теперь ошибки уменьшаются до

otherhint.py:29: error: Incompatible types in assignment (expression has type "complex", variable has type "float")
otherhint.py:30: error: Incompatible types in assignment (expression has type "complex", variable has type "int")

, что в точности противоположно тому, что мы хотим, но если вы заботитесь только о PyCharm, это не имеет значения.

0 голосов
/ 16 апреля 2020

Хорошо, я смог поиграть и объединить ответы @AntonPomieshcheko и @KevinLanguasco, чтобы найти решение, в котором:

  • Моя IDE (PyCharm) может правильно определить тип возврата
  • mypy сообщает о наличии несоответствия типов
  • Не выдает ошибки во время выполнения, даже если подсказки типов указывают на несоответствие

Это именно такое поведение Я хотел. Большое спасибо всем:)

#!/usr/bin/env python3

from typing import TypeVar, Generic, ClassVar, Callable


T = TypeVar("T", float, int)  # types supported


class SomeBaseClass(Generic[T]):
    """This base class's some_method will return a supported type."""

    RET_TYPE: ClassVar[Callable]

    def some_method(self, some_input: str) -> T:
        return self.RET_TYPE(some_input)


class SomeChildClass1(SomeBaseClass[float]):
    """This child class's some_method will return a float."""

    RET_TYPE = float


class SomeChildClass2(SomeBaseClass[int]):
    """This child class's some_method will return an int."""

    RET_TYPE = int


class SomeChildClass3(SomeBaseClass[complex]):
    """This child class's some_method will return a complex."""

    RET_TYPE = complex


if __name__ == "__main__":
    some_class_1_ret: float = SomeChildClass1().some_method("42")
    some_class_2_ret: int = SomeChildClass2().some_method("42")

    # PyCharm can infer this return is a complex.  However, running mypy on
    # this will report (this is desirable to me):
    # error: Value of type variable "T" of "SomeBaseClass" cannot be "complex"
    some_class_3_ret = SomeChildClass3().some_method("42")

    print(
        f"some_class_1_ret = {some_class_1_ret} of type {type(some_class_1_ret)}\n"
        f"some_class_2_ret = {some_class_2_ret} of type {type(some_class_2_ret)}\n"
        f"some_class_3_ret = {some_class_3_ret} of type {type(some_class_3_ret)}\n"
    )
0 голосов
/ 14 апреля 2020

Вы можете просто использовать что-то подобное:

from typing import TypeVar, Generic


T = TypeVar('T', float, int) # types you support


class SomeClass(Generic[T]):
    """This class's some_method will return float."""

    RET_TYPE = float

    def some_method(self, some_input: str) -> T:
        return self.RET_TYPE(some_input)


class SomeChildClass(SomeClass[int]):
    """This class's some_method will return int."""

    RET_TYPE = int


if __name__ == "__main__":
    ret: int = SomeChildClass().some_method("42")
    ret2: float = SomeChildClass().some_method("42")

Но есть одна проблема. Это я не знаю, как решить. Для метода SomeChildClass в IDE some_method будет отображаться общая подсказка c. По крайней мере, pycharm (я полагаю, вы это) не показывает это как ошибку.

...