【问题标题】:Having trouble getting mypy to recognize that variable implements a protocol无法让 mypy 识别该变量实现协议
【发布时间】:2022-01-24 02:14:03
【问题描述】:

以下代码在使用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:错误:“track”的参数 1 具有不兼容的类型“List[X]”;预期“列表[原型]”

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

【问题讨论】:

    标签: python protocols mypy


    【解决方案1】:

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

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

    items = [y, z]
    

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

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

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

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

    【讨论】:

    • 思考 mypy 如何可能进行更详细的推理是很有趣的,例如通过编译yz 实现的所有协议的交集,并将其包括在items 的推断类型中。在实践中,我认为这是一个糟糕的权衡(与仅显式执行类型注释相比),因为在每种情况下拥有所有额外的类型信息会使全面调试变得更加困难。
    • 在这种情况下,我是否必须在声明时指定类型?或者有没有办法“欺骗”mypy 来推断正确的类型??
    • 在声明items时赋值即可。还有其他方法可以做到这一点,但它们不会让您的生活更轻松。
    • 我已经接受了你的回答,但我想我还是想知道这些其他方式可能是什么样子。
    • 一种方法是使用track([y, z], 40) 而不是首先分配items。适用于此 sn-p,但不适用于一般情况。另一种方法是声明一个继承XProto 的超类,并让YZ 继承自它,因此推断的类型成为符合track 参数的东西。不过,您只是将显式声明移到其他地方,然后您就不再真正使用协议了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-03
    • 2022-01-13
    • 2022-01-23
    • 1970-01-01
    • 2022-10-04
    • 2011-12-16
    相关资源
    最近更新 更多