具有自类型的通用协议中的类方法,mypy 类型检查失败
Classmethods in Generic Protocols with self-types, mypy type checking failure
一点背景,我基本上需要定义一个 int
包装器类型,比如 MyInt
(以及其他一些 class 类型)和另一个通用 Interval
类型它可以接受 MyInt
对象以及其他类型的对象。由于 Interval
可接受的类型不属于整齐的层次结构,我认为这将是实验性 Protocol
的完美用例,在我的情况下,它需要几个方法和一个几个 @classmethod
秒。所有方法 return a "self-type",即 MyInt.my_method
returns a 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
这对我来说很难理解。为什么 return 类型需要 <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
一点也不抱怨:
我读过 self-types in protocols in 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
此外,in PEP484, regarding typing classmethods,我们看到这个例子:
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
/ class 定义有什么问题?我错过了一些明显的东西吗?对于有关失败原因 或任何解决方法的任何具体答案,我将不胜感激。但请注意,我需要这些属性可以在 class 上访问。
请注意,我试过使用 ClassVar
,但这引入了其他问题...即 ClassVar
不接受类型变量 as far as I can tell ClassVar
's cannot be generic。理想情况下,这将是一个 @classmethod
,因为我可能不得不依赖我想放入 class.
中的其他元数据
我不是 Mypy 专家,但最近一直在自学使用它,我认为这可能是由于此处提到的 Mypy 中的一个问题所致:
https://github.com/python/mypy/issues/3645
问题在于在 class 方法中处理 TypeVar 变量,而不是与协议直接相关的任何事情。
link 中给出了以下最小示例来说明问题。
T = TypeVar('T')
class Factory(Generic[T]):
def produce(self) -> T:
...
@classmethod
def get(cls) -> T:
return cls().produce()
class HelloWorldFactory(Factory[str]):
def produce(self) -> str:
return 'Hello World'
reveal_type(HelloWorldFactory.get()) # mypy should be able to infer 'str' here
reveal_type 的输出是 T 而不是 str。您的代码也发生了同样的事情,Mypy 未能推断出类型应该是 MyInteger
而不是 _P
,因此没有将您的 class 视为实现协议。将 class 方法的 return 类型更改为 'PType'
会使错误消失,但我没有足够的信心知道该更改是否有任何其他影响。
已经就如何最好地处理它进行了一些讨论,因为在每种情况下决定正确的行为应该是什么并不是微不足道的,所以向他们标记这个以获取更多用例示例可能没有害处(请参阅 [=例如 15=]。)
一点背景,我基本上需要定义一个 int
包装器类型,比如 MyInt
(以及其他一些 class 类型)和另一个通用 Interval
类型它可以接受 MyInt
对象以及其他类型的对象。由于 Interval
可接受的类型不属于整齐的层次结构,我认为这将是实验性 Protocol
的完美用例,在我的情况下,它需要几个方法和一个几个 @classmethod
秒。所有方法 return a "self-type",即 MyInt.my_method
returns a 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
这对我来说很难理解。为什么 return 类型需要 <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
一点也不抱怨:
我读过 self-types in protocols in 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
此外,in PEP484, regarding typing classmethods,我们看到这个例子:
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
/ class 定义有什么问题?我错过了一些明显的东西吗?对于有关失败原因 或任何解决方法的任何具体答案,我将不胜感激。但请注意,我需要这些属性可以在 class 上访问。
请注意,我试过使用 ClassVar
,但这引入了其他问题...即 ClassVar
不接受类型变量 as far as I can tell ClassVar
's cannot be generic。理想情况下,这将是一个 @classmethod
,因为我可能不得不依赖我想放入 class.
我不是 Mypy 专家,但最近一直在自学使用它,我认为这可能是由于此处提到的 Mypy 中的一个问题所致:
https://github.com/python/mypy/issues/3645
问题在于在 class 方法中处理 TypeVar 变量,而不是与协议直接相关的任何事情。
link 中给出了以下最小示例来说明问题。
T = TypeVar('T')
class Factory(Generic[T]):
def produce(self) -> T:
...
@classmethod
def get(cls) -> T:
return cls().produce()
class HelloWorldFactory(Factory[str]):
def produce(self) -> str:
return 'Hello World'
reveal_type(HelloWorldFactory.get()) # mypy should be able to infer 'str' here
reveal_type 的输出是 T 而不是 str。您的代码也发生了同样的事情,Mypy 未能推断出类型应该是 MyInteger
而不是 _P
,因此没有将您的 class 视为实现协议。将 class 方法的 return 类型更改为 'PType'
会使错误消失,但我没有足够的信心知道该更改是否有任何其他影响。
已经就如何最好地处理它进行了一些讨论,因为在每种情况下决定正确的行为应该是什么并不是微不足道的,所以向他们标记这个以获取更多用例示例可能没有害处(请参阅 [=例如 15=]。)