Немного предыстории, мне необходимо определить тип оболочки int
, скажем MyInt
(среди некоторых других классов), и другой универсальный тип Interval
, который может принимать MyInt
объекты, а также другие типы объекты. Так как типы, приемлемые Interval
, не попадают в аккуратную иерархию, я подумал, что это будет идеальный вариант использования для экспериментального Protocol
, который в моем случае потребует пару методов и пару @classmethod
s. Все методы возвращают «тип», то есть MyInt.my_method
возвращает MyInt
. Вот MCVE:
from dataclasses import dataclass
from typing import Union, ClassVar, TypeVar, Generic, Type
from typing_extensions import Protocol
_P = TypeVar('_P', bound='PType')
class PType(Protocol):
@classmethod
def maximum_type_value(cls: Type[_P]) -> _P:
...
@classmethod
def minimum_type_value(cls: Type[_P]) -> _P:
...
def predecessor(self: _P) -> _P:
...
def successor(self: _P) -> _P:
...
@dataclass
class MyInteger:
value: int
_MAX: ClassVar[int] = 42
_MIN: ClassVar[int] = -42
def __post_init__(self) -> None:
if not (self._MIN <= self.value <= self._MAX):
msg = f"Integers must be in range [{self._MIN}, {self._MAX}]"
raise ValueError(msg)
@classmethod
def maximum_type_value(cls) -> MyInteger:
return MyInteger(cls._MAX)
@classmethod
def minimum_type_value(cls) -> MyInteger:
return MyInteger(cls._MIN)
def predecessor(self) -> MyInteger:
return MyInteger(self.value - 1)
def successor(self) -> MyInteger:
return MyInteger(self.value + 1)
@dataclass
class Interval(Generic[_P]):
low: _P
high: _P
interval = Interval(MyInteger(1), MyInteger(2))
def foo(x: PType) -> PType:
return x
foo(MyInteger(42))
Однако, Mypy жалуется:
(py37) Juans-MacBook-Pro: juan$ mypy mcve.py
mcve.py:46: error: Value of type variable "_P" of "Interval" cannot be "MyInteger"
mcve.py:49: error: Argument 1 to "foo" has incompatible type "MyInteger"; expected "PType"
mcve.py:49: note: Following member(s) of "MyInteger" have conflicts:
mcve.py:49: note: Expected:
mcve.py:49: note: def maximum_type_value(cls) -> <nothing>
mcve.py:49: note: Got:
mcve.py:49: note: def maximum_type_value(cls) -> MyInteger
mcve.py:49: note: Expected:
mcve.py:49: note: def minimum_type_value(cls) -> <nothing>
mcve.py:49: note: Got:
mcve.py:49: note: def minimum_type_value(cls) -> MyInteger
Что мне трудно понять. Почему тип возврата ожидает <nothing>
? Я пытался просто не аннотировать cls
в протоколе:
_P = TypeVar('_P', bound='PType')
class PType(Protocol):
@classmethod
def maximum_type_value(cls) -> _P:
...
@classmethod
def minimum_type_value(cls) -> _P:
...
def predecessor(self: _P) -> _P:
...
def successor(self: _P) -> _P:
...
Однако mypy выдает похожее сообщение об ошибке:
mcve.py:46: error: Value of type variable "_P" of "Interval" cannot be "MyInteger"
mcve.py:49: error: Argument 1 to "foo" has incompatible type "MyInteger"; expected "PType"
mcve.py:49: note: Following member(s) of "MyInteger" have conflicts:
mcve.py:49: note: Expected:
mcve.py:49: note: def [_P <: PType] maximum_type_value(cls) -> _P
mcve.py:49: note: Got:
mcve.py:49: note: def maximum_type_value(cls) -> MyInteger
mcve.py:49: note: Expected:
mcve.py:49: note: def [_P <: PType] minimum_type_value(cls) -> _P
mcve.py:49: note: Got:
mcve.py:49: note: def minimum_type_value(cls) -> MyInteger
Что для меня, имеет еще меньше смысла. Обратите внимание, если я сделаю эти методы экземпляра:
_P = TypeVar('_P', bound='PType')
class PType(Protocol):
def maximum_type_value(self: _P) -> _P:
...
def minimum_type_value(self: _P) -> _P:
...
def predecessor(self: _P) -> _P:
...
def successor(self: _P) -> _P:
...
@dataclass
class MyInteger:
value: int
_MAX: ClassVar[int] = 42
_MIN: ClassVar[int] = -42
def __post_init__(self) -> None:
if not (self._MIN <= self.value <= self._MAX):
msg = f"Integers must be in range [{self._MIN}, {self._MAX}]"
raise ValueError(msg)
def maximum_type_value(self) -> MyInteger:
return MyInteger(self._MAX)
def minimum_type_value(self) -> MyInteger:
return MyInteger(self._MIN)
def predecessor(self) -> MyInteger:
return MyInteger(self.value - 1)
def successor(self) -> MyInteger:
return MyInteger(self.value + 1)
Тогда mypy
совсем не жалуется:
Я прочитал о самоподтипах в протоколах в PEP 544 , где приведен следующий пример:
C = TypeVar('C', bound='Copyable')
class Copyable(Protocol):
def copy(self: C) -> C:
class One:
def copy(self) -> 'One':
...
T = TypeVar('T', bound='Other')
class Other:
def copy(self: T) -> T:
...
c: Copyable
c = One() # OK
c = Other() # Also OK
Кроме того, в PEP484, относительно набора методов класса , мы видим этот пример:
T = TypeVar('T', bound='C')
class C:
@classmethod
def factory(cls: Type[T]) -> T:
# make a new instance of cls
class D(C): ...
d = D.factory() # type here should be D
Что не так с моим Protocol
/ определением класса? Я что-то упускаю из виду? Я был бы признателен за любые конкретные ответы о , почему это не удается , или любой обходной путь. Но обратите внимание, мне нужно, чтобы эти атрибуты были доступны в классе.
Обратите внимание, я пытался использовать ClassVar
, но это привело к другим проблемам ... а именно, ClassVar
не принимает переменные типа , насколько я могу судить, ClassVar
не может быть родовое . И в идеале это будет @classmethod
, поскольку мне, возможно, придется полагаться на другие метаданные, которые я хотел бы использовать в классе.