英文:
Why is a type identified as not itself in mypy?
问题
MRE: https://mypy-play.net/?mypy=latest&python=3.10&gist=263dfa4914d0317291638957e51e8700&flags=strict
from typing import Callable, TypeVar, Type, Protocol
from typing_extensions import Self
_CT = TypeVar("_CT")
_T = TypeVar("_T")
class LtConverter(Protocol):
def __new__(cls: Type[_T], x: _CT) -> _T: ...
def __lt__(self, other: Self) -> bool: ...
def lt_through(conv: Type[LtConverter]) -> Callable[[_CT, _CT], bool]:
def comparator(op: Callable[[LtConverter, LtConverter], bool]) -> Callable[[_CT, _CT], bool]:
def method(self: _CT, other: _CT) -> bool:
return op(conv(self), conv(other))
return method
return comparator(lambda x, y: x < y)
在MRE中,lt_through(conv)
返回一个函数,可以用作类的方法,使得类的“小于”运算符比较类在被转换为类型conv
之后的结果,其中conv
是支持协议进行转换和自身的小于操作的任何类。
mypy给出以下错误消息:
main.py:18: error: Argument 1 to "LtConverter" has incompatible type "_CT"; expected "_CT" [arg-type]
我对这条消息感到有些困惑。为什么_CT
被认为与_CT
不同呢?为什么这个错误发生在conv(self)
而不是conv(other)
上(可以通过查看详细输出中的字符编号来看出)?
英文:
from typing import Callable, TypeVar, Type, Protocol
from typing_extensions import Self
_CT = TypeVar("_CT")
_T = TypeVar("_T")
class LtConverter(Protocol):
def __new__(cls: Type[_T], x: _CT) -> _T: ...
def __lt__(self, other: Self) -> bool: ...
def lt_through(conv: Type[LtConverter]) -> Callable[[_CT, _CT], bool]:
def comparator(op: Callable[[LtConverter, LtConverter], bool]) -> Callable[[_CT, _CT], bool]:
def method(self: _CT, other: _CT) -> bool:
return op(conv(self), conv(other))
return method
return comparator(lambda x, y: x < y)
In the MRE, lt_through(conv)
returns a function, which can be used as a method for a class, such that the "less than" operator of the class compares the class after been casted to type conv
, which is any class supporting the protocol to casting and the less than operation with itself.
mypy gives the following error message:
main.py:18: error: Argument 1 to "LtConverter" has incompatible type "_CT"; expected "_CT" [arg-type]
I am vaguely confused by this message. Why is _CT
considered to be different from _CT
? Why does this error occur for conv(self)
but not for conv(other)
(which can be seen by inspecting the char number in the verbose output)?
答案1
得分: 1
您的代码几乎是正确的 - mypy的报告只是没有很好地允许轻松修复剩下的问题。我将首先回答这两个问题:
-
"我对这条消息感到有点困惑。为什么
_CT
被认为与_CT
不同?"在
def __new__(cls: Type[_T], x: _CT) -> _T: ...
中的_CT
实际上与在def method(self: _CT, other: _CT) -> bool:
中找到的_CT
不是同一个_CT
- 你只是在两个不同的类型变量上使用了相同的名称_CT
。重写这个实现,这是 mypy 实际上看到你正在做的事情:_CT1 = TypeVar(" _CT1 ") _CT2 = TypeVar(" _CT2 ") ... class LtConverter(Protocol): def __new__(cls: Type[_T], x: _CT1) -> _T: ... ... def method(self: _CT2, other: _CT2) -> bool: return op(conv(self), conv(other)) # mypy: Argument 1 to "LtConverter" has incompatible type "_CT2"; expected "_CT1" [arg-type]
-
"为什么这个错误发生在 conv(self) 上,而不是 conv(other) 上(可以通过查看详细输出中的字符数来看到)?"
非常不幸,这是因为 mypy 有时无法报告一行上的多个错误。如果稍微重新格式化代码,就会出现第二个错误,就像下面这样:
def method(self: _CT, other: _CT) -> bool: return op( conv(self), # mypy: Argument 1 to "LtConverter" has incompatible type "_CT"; expected "_CT" [arg-type] conv(other), # mypy: Argument 1 to "LtConverter" has incompatible type "_CT"; expected "_CT" [arg-type] )
如果我正确理解你的意图,要修复这个问题,你只需要将 LtConverter.__new__
更改为以下之一:
def __new__(cls: Type[_T], x: typing.Any) -> _T: ...
def __new__(cls, x: typing.Any) -> Self: ...
这是基于以下假设的:
def method(self: _CT, other: _CT):
意味着 我不关心self
和other
是什么,只要它们可以被上升到相同的类型,以及LtConverter
能够被任何对象实例化以提供有效的__lt__
比较。
实际上,你的预期实现可能会比这个严格一些,如果是这样,你应该考虑在 _CT
上设置类型变量边界,用 _CT
类型参数化 LtConverter
,和/或用 x: _CT
替换 LtConverter.__new__::x: typing.Any
。以下是一个可能的更严格类型化版本:
from typing import Callable, TypeVar, Type, Protocol
from typing_extensions import Self
_CT = TypeVar("_CT")
_T = TypeVar("_T")
_ConverteeT = TypeVar("_ConverteeT", covariant=True)
class LtConverter(Protocol[_ConverteeT]):
def __new__(cls: Type[_T], x: _ConverteeT) -> _T: ...
def __lt__(self, other: Self) -> bool: ...
def lt_through(conv: Type[LtConverter[_CT]]) -> Callable[[_CT, _CT], bool]:
def comparator(op: Callable[[LtConverter[_CT], LtConverter[_CT]], bool]) -> Callable[[_CT, _CT], bool]:
def method(self: _CT, other: _CT) -> bool:
return op(conv(self), conv(other))
return method
return comparator(lambda x, y: x < y)
英文:
Your code is almost correct - mypy's reporting just didn't do very well to allow easily fixing the remaining issues. I'll answer these two first:
-
> I am vaguely confused by this message. Why is
_CT
considered to be different from_CT
?The
_CT
indef __new__(cls: Type[_T], x: _CT) -> _T: ...
is not actually the same_CT
as the one found indef method(self: _CT, other: _CT) -> bool:
- you've just used the same name_CT
in two different type variable contexts. Rewriting this implementation, this is what mypy actually sees you're doing:_CT1 = TypeVar("_CT1") _CT2 = TypeVar("_CT2") ... class LtConverter(Protocol): def __new__(cls: Type[_T], x: _CT1) -> _T: ... ... def method(self: _CT2, other: _CT2) -> bool: return op(conv(self), conv(other)) # mypy: Argument 1 to "LtConverter" has incompatible type "_CT2"; expected "_CT1" [arg-type]
-
> Why does this error occur for conv(self) but not for conv(other) (which can be seen by inspecting the char number in the verbose output)?
Very unfortunately, this is because mypy sometimes can't report multiple errors on one line. You'll get the second one appear if you reformat the code a bit, like the following:
def method(self: _CT, other: _CT) -> bool: return op( conv(self), # mypy: Argument 1 to "LtConverter" has incompatible type "_CT"; expected "_CT" [arg-type] conv(other), # mypy: Argument 1 to "LtConverter" has incompatible type "_CT"; expected "_CT" [arg-type] )
If I understand your intention correctly, to fix this, you just have to change LtConverter.__new__
to one of the following:
def __new__(cls: Type[_T], x: typing.Any) -> _T: ...
def __new__(cls, x: typing.Any) -> Self: ...
This is based on the assumptions that
def method(self: _CT, other: _CT):
means I don't care whatself
andother
are, as long as they're up-castable to the same type, andLtConverter
is capable of being instantiated with any object to provide a valid__lt__
comparison.
In practice, your intended implementation might be a bit stricter than this, in which case you should consider putting a type variable bound on _CT
, parameterising LtConverter
with a _CT
type, and/or replacing LtConverter.__new__::x: typing.Any
with x: _CT
. Here's one possible version with stricter typing:
from typing import Callable, TypeVar, Type, Protocol
from typing_extensions import Self
_CT = TypeVar("_CT")
_T = TypeVar("_T")
_ConverteeT = TypeVar("_ConverteeT", covariant=True)
class LtConverter(Protocol[_ConverteeT]):
def __new__(cls: Type[_T], x: _ConverteeT) -> _T: ...
def __lt__(self, other: Self) -> bool: ...
def lt_through(conv: Type[LtConverter[_CT]]) -> Callable[[_CT, _CT], bool]:
def comparator(op: Callable[[LtConverter[_CT], LtConverter[_CT]], bool]) -> Callable[[_CT, _CT], bool]:
def method(self: _CT, other: _CT) -> bool:
return op(conv(self), conv(other))
return method
return comparator(lambda x, y: x < y)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论