微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

通用协议:mypy 错误:参数 1 的类型不兼容……;预期的

如何解决通用协议:mypy 错误:参数 1 的类型不兼容……;预期的

我正在尝试实现通用协议。我的意图是拥有一个带有简单 getter 的 Widget[key_type,value_type] 协议。 Mypy 抱怨 Protocol[K,T],因此变成了 Protocol[K_co,T_co]。我已经去除了所有其他约束,但我什至无法让最基本的情况 widg0: Widget[Any,Any] = ActualWidget() 发挥作用。 ActualWidget.get 应该与 get(self,key: K) -> Any 完全兼容,这让我觉得我在某种程度上使用了泛型/协议错误,或者 mypy 无法处理这个。

来自 mypy 的命令/错误

$ mypy cat_example.py
cat_example.py:34: error: Argument 1 to "takes_widget" has incompatible type "ActualWidget"; expected "Widget[Any,Any]"
cat_example.py:34: note: Following member(s) of "ActualWidget" have conflicts:
cat_example.py:34: note:     Expected:
cat_example.py:34: note:         def [K] get(self,key: K) -> Any
cat_example.py:34: note:     Got:
cat_example.py:34: note:         def get(self,key: str) -> Cat
Found 1 error in 1 file (checked 1 source file)

或者,如果我尝试使用 widg0: Widget[Any,Any] = ActualWidget() 强制分配:

error: Incompatible types in assignment (expression has type "ActualWidget",variable has type "Widget[Any,Any]")

完整代码

from typing import Any,TypeVar
from typing_extensions import Protocol,runtime_checkable

K = TypeVar("K")  # ID/Key Type
T = TypeVar("T")  # General type
K_co = TypeVar("K_co",covariant=True)  # ID/Key Type or subclass
T_co = TypeVar("T_co",covariant=True)  # General type or subclass
K_contra = TypeVar("K_contra",contravariant=True)  # ID/Key Type or supertype
T_contra = TypeVar("T_contra",contravariant=True)  # General type or supertype

class Animal(object): ...

class Cat(Animal): ...


@runtime_checkable
class Widget(Protocol[K_co,T_co]):
    def get(self,key: K) -> T_co: ...

class ActualWidget(object):
    def get(self,key: str) -> Cat:
        return Cat()

def takes_widget(widg: Widget):
    return widg

if __name__ == '__main__':
    widg0 = ActualWidget()
    #widg0: Widget[str,Cat] = ActualWidget()
    #widg0: Widget[Any,Any] = ActualWidget()

    print(isinstance(widg0,Widget))
    print(isinstance({},Widget))
    takes_widget(widg0)

解决方法

把我的评论写在这里。

要使您的问题示例起作用,您需要使输入参数 逆变 和输出参数 协变 像这样:

from typing import TypeVar
from typing_extensions import Protocol,runtime_checkable

T_co = TypeVar("T_co",covariant=True)  # General type or subclass
K_contra = TypeVar("K_contra",contravariant=True)  # ID/Key Type or supertype

class Animal: ...

class Cat(Animal): ...

@runtime_checkable
class Widget(Protocol[K_contra,T_co]):
    def get(self,key: K_contra) -> T_co: ...

class ActualWidget:
    def get(self,key: str) -> Cat:
        return Cat()

def takes_widget(widg: Widget):
    return widg

class StrSub(str):
    pass

if __name__ == '__main__':
    widget_0: Widget[str,Cat] = ActualWidget()
    widget_1: Widget[StrSub,Cat] = ActualWidget()
    widget_2: Widget[str,object] = ActualWidget()
    widget_3: Widget[StrSub,object] = ActualWidget()

    takes_widget(widget_0)
    takes_widget(widget_1)
    takes_widget(widget_2)
    takes_widget(widget_3)

ActualWidget() 是一个 Widget[str,Cat],然后可以将 Widget[SubStr,object]widget_3 赋值给 Widget[str,Cat],这意味着 Widget[SubStr,object]Widget[str,Cat] 的子类.

SubStr 可以采用所有 str 加上其他 object 子类型(子类关系中的输入类型可以不那么具体,因此是逆变的)并且可以具有至少为str,加上具有 Animal -> Cat 属性(子类关系中的输出类型可以更具体,因此是协方差)。另请参阅 Wikipedia - Function Types,它使此观察形式化:

例如,Cat -> CatAnimal -> AnimalCat -> Animal 类型的函数可以在需要 {{1}} 的任何地方使用。

换句话说,→ 类型构造函数在参数(输入)类型中是逆变的,在返回(输出)类型中是协变的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。