无法让 mypy 识别变量实现协议

Having trouble getting mypy to recognize that variable implements a protocol

当 运行 与 mypy --strict

时,以下代码会产生错误
from typing import Protocol

class Proto(Protocol):
  id: int
  name: str
  def work(self, hours: float) -> str: ...  

class RoleA:
  def work(self, hours: float) -> str:
    return f"A {hours}"

class RoleB:
  def work(self, hours: float) -> str:
    return f"B {hours}"

class X:
  def __init__(self, id: int, name: str) -> None:
    self.id = id
    self.name = name

class Y(X, RoleA):
  def __init__(self, id: int, name: str) -> None:
    super().__init__(id, name)

class Z(X, RoleB):
  def __init__(self, id: int, name: str) -> None:
    super().__init__(id, name)

def track(items: list[Proto], hours: float) -> None:
  for item in items:
    result = item.work(hours)


if __name__ == "__main__":
  y = Y(id = 4, name = "Jane Doe")
  z = Z(id = 3, name = "Kevin Bacon")
  items =  [y, z]
  track(items, 40)

错误如下:

program.py:38: error: Argument 1 to "track" has incompatible type "List[X]"; expected "List[Proto]"

我不明白为什么会这样,据我所知 YZ 都实现了 Proto 协议,那么为什么 mypy 能够推断出它们是 track 函数的有效参数吗?

每个变量在声明时都会绑定一个特定的类型。对于没有注释的 non-generic 类型,它只是赋值右侧的对象类型,这几乎总是正确的(除非您打算稍后将其重新分配给不同的类型,在这种情况下您需要事先将其声明为联合,以便它可以接受任何一种类型)。

当您没有显式声明泛型类型(如列表或其他集合)的类型参数时:

items = [y, z]

mypy 对此做出了最好的猜测。在本例中是 List[X],因为 Xyz 中最明显的常见类型。绝大多数情况下推断的类型都是您想要的类型,但有时它要么太宽泛要么太狭窄,在这种情况下您可以明确指定一个:

items: list[Proto] = [y, z]  # or typing.List[Proto] for python <3.10

这将修复您的 track 调用中的错误。

通常我认为 mypy 不会将 Protocol 类型推断为多个对象的通用类型(因为给定对象可能实现任意数量的协议),因此您需要声明它明确地以一种或另一种方式。