Первый совет: если mypy
недостаточно говорит вам, попробуйте mypy --strict
.
. Вы правильно поняли, что аннотация типа для op
в базовом классе недостаточно ограничительна и фактически будетнесовместим с дочерним классом.
Посмотрите на этот нерабочий пример.
from __future__ import annotations
from abc import ABC, abstractmethod
class AbsGroup(ABC):
@abstractmethod
def op(self, other: AbsGroup) -> AbsGroup:
pass
class Bool(AbsGroup):
def __init__(self, val: str = "False") -> None:
self.val = val
def op(self, other: Bool) -> Bool:
...
Я аннотировал op
в Bool
с правильным типомно теперь mypy жалуется:
file.py: 15: ошибка: Аргумент 1 из "op" несовместим с супертипом "AbsGroup";supertype определяет тип аргумента как «AbsGroup»
У вас есть два варианта: либо сделать базовую аннотацию еще менее строгой (Any
), либо сделать ваш класс Generic
one:
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TypeVar, Generic
T = TypeVar('T')
class AbsGroup(Generic[T], ABC):
@abstractmethod
def op(self, other: T) -> T:
pass
# EDIT: ADDED QUOTES AROUND Bool
class Bool(AbsGroup['Bool']):
def __init__(self, val: str = "False") -> None:
self.val = val
def op(self, other: Bool) -> Bool:
...
Это включает в себя несколько шагов:
- создание переменной типа
T
(похоже на переменные универсального типа в других языках) - пусть базовый класс также наследуется от
Generic[T]
делая его универсальным классом - измените метод
op
, чтобы он брал и возвращал T
- , чтобы дочерний класс наследовал от
AbsGroup[Bool]
(в C ++ это известно как CRTP )
Это заставляет замолчать mypy --strict
, и PyCharm правильно выводит тип возврата op
.
Редактировать:
Предыдущее определение дочернего класса выглядело так: class Bool(AbsGroup[Bool]): ...
без кавычек. Но это не работает и выдает NameError
при создании класса:
NameError: имя 'Bool' не определено
Это ожидаемое поведение, как написанов PEP 563 .
[...] Однако в модуле ввода есть API-интерфейсы, которые используют другие синтаксические конструкции языка, и для них по-прежнему потребуется обходной путь прямые ссылки с строковыми литералами . Список включает в себя: [...]
Так чтов этом случае все еще требуются кавычки, хотя мы использовали будущий импорт.
Просто примечание: почему вы используете строковые символы для логических значений? Уже есть два прекрасно работающих экземпляра с именами True
и False
. Это сделает ваш код намного проще. Например, проверка в конструкторе может быть упрощена до if type(val) is bool
(я бы не стал использовать isinstance
здесь, поскольку вы не хотите, чтобы val
был пользовательским типом, возможно?).