为什么在mypy中一个类型被识别为不是它本身?

huangapple go评论54阅读模式
英文:

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)上(可以通过查看详细输出中的字符编号来看出)?

英文:

MRE: https://mypy-play.net/?mypy=latest&amp;python=3.10&amp;gist=263dfa4914d0317291638957e51e8700&amp;flags=strict

from typing import Callable, TypeVar, Type, Protocol

from typing_extensions import Self

_CT = TypeVar(&quot;_CT&quot;)
_T = TypeVar(&quot;_T&quot;)


class LtConverter(Protocol):
    def __new__(cls: Type[_T], x: _CT) -&gt; _T: ...

    def __lt__(self, other: Self) -&gt; bool: ...


def lt_through(conv: Type[LtConverter]) -&gt; Callable[[_CT, _CT], bool]:
    def comparator(op: Callable[[LtConverter, LtConverter], bool]) -&gt; Callable[[_CT, _CT], bool]:
        def method(self: _CT, other: _CT) -&gt; bool:
            return op(conv(self), conv(other))
        return method
    return comparator(lambda x, y: x &lt; 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 &quot;LtConverter&quot; has incompatible type &quot;_CT&quot;; expected &quot;_CT&quot;  [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): 意味着 我不关心 selfother 是什么,只要它们可以被上升到相同的类型,以及
  • 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 in def __new__(cls: Type[_T], x: _CT) -&gt; _T: ... is not actually the same _CT as the one found in def method(self: _CT, other: _CT) -&gt; 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(&quot;_CT1&quot;)
    _CT2 = TypeVar(&quot;_CT2&quot;)
    
    ...
    
    class LtConverter(Protocol):
        def __new__(cls: Type[_T], x: _CT1) -&gt; _T:
            ...
    
    ...
    
        def method(self: _CT2, other: _CT2) -&gt; bool:
             return op(conv(self), conv(other))  # mypy: Argument 1 to &quot;LtConverter&quot; has incompatible type &quot;_CT2&quot;; expected &quot;_CT1&quot; [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) -&gt; bool:
              return op(
                  conv(self),  # mypy: Argument 1 to &quot;LtConverter&quot; has incompatible type &quot;_CT&quot;; expected &quot;_CT&quot; [arg-type]
                  conv(other),  # mypy: Argument 1 to &quot;LtConverter&quot; has incompatible type &quot;_CT&quot;; expected &quot;_CT&quot; [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) -&gt; _T: ...
  • def __new__(cls, x: typing.Any) -&gt; Self: ...

This is based on the assumptions that

  • def method(self: _CT, other: _CT): means I don't care what self and other are, as long as they're up-castable to the same type, and
  • LtConverter 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(&quot;_CT&quot;)
_T = TypeVar(&quot;_T&quot;)
_ConverteeT = TypeVar(&quot;_ConverteeT&quot;, covariant=True)


class LtConverter(Protocol[_ConverteeT]):
    def __new__(cls: Type[_T], x: _ConverteeT) -&gt; _T: ...

    def __lt__(self, other: Self) -&gt; bool: ...


def lt_through(conv: Type[LtConverter[_CT]]) -&gt; Callable[[_CT, _CT], bool]:
    def comparator(op: Callable[[LtConverter[_CT], LtConverter[_CT]], bool]) -&gt; Callable[[_CT, _CT], bool]:
        def method(self: _CT, other: _CT) -&gt; bool:
            return op(conv(self), conv(other))
        return method
    return comparator(lambda x, y: x &lt; y)

huangapple
  • 本文由 发表于 2023年5月29日 10:24:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76354345.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定