【问题标题】:Determining why a particular object does not implement a Protocol确定特定对象为什么不实现协议
【发布时间】:2020-06-16 08:19:50
【问题描述】:

假设我定义了一个协议Frobbable。此外,我还有一个有效的协议实现,以及一个缺少.frob() 方法的损坏实现:

from typing import Protocol
from abc import abstractmethod


class Frobbable(Protocol):
    @abstractmethod
    def frob(self) -> None:
        raise NotImplementedError


def main(knob: Frobbable) -> None:
    knob.frob()


class Knob:
    def frob(self) -> None:
        print("knob has been frobbed")


class BrokenKnob:
    pass


main(Knob())
main(BrokenKnob())

使用 mypy 检查该程序会导致错误,正如预期的那样:

testprotocol.py:25:6: error: Argument 1 to "main" has incompatible type "BrokenKnob"; expected "Frobbable"  [arg-type]
    main(BrokenKnob())
         ^
Found 1 error in 1 file (checked 1 source file)

不幸的是,它没有提供任何关于为什么BrokenKnob 不兼容的信息:在这种情况下,它缺少.frob()。如果没有这些信息,在不平凡的程序(使用具有多种方法和多种实现的协议)中纠正此类问题将变得非常乏味。

有没有办法在不修改程序的情况下从 mypy 或任何其他工具 获取这些信息?我知道我可以显式继承 Frobbable,但这完全违背了使用 Protocol 的目的。

【问题讨论】:

  • 我不确定一个协议是否意味着那么复杂。为什么一个协议需要很多方法?
  • @chepner 为什么任何对象都需要不止一种方法?如果你尝试的话,我相信你能想到一些原因。但问题不在于这个。
  • 协议比普通课程更专注。如果您传递给main 的东西非常具体以至于它需要支持许多方法,那么您可能需要一个特定的(子)类,而不是协议。

标签: python mypy


【解决方案1】:

我认为这是由于 mypy 用于列出 missing protocol members 的启发式方法的限制。通常,mypy 应该报告任何丢失的协议成员,但为了避免产生过多的垃圾邮件错误消息,如果每个协议成员都丢失或丢失的成员数量超过 2,它就不会这样做。

例如,如果我们调整您的示例,使其符合这些限制...

from typing import Protocol
from abc import abstractmethod


class Frobbable(Protocol):
    @abstractmethod
    def frob(self) -> None:
        raise NotImplementedError
    @abstractmethod
    def bob(self) -> None:
        raise NotImplementedError


def main(knob: Frobbable) -> None:
    knob.frob()


class BrokenKnob:
    def frob(self) -> None:
        raise NotImplementedError


main(BrokenKnob())

...我们按预期得到更具描述性的错误消息:

test.py:23: error: Argument 1 to "main" has incompatible type "BrokenKnob"; expected "Frobbable"
test.py:23: note: 'BrokenKnob' is missing following 'Frobbable' protocol member:
test.py:23: note:     bob

虽然这些启发式方法看起来确实很合理,但我也认为它们可能需要进行更多改进,以更好地处理您遇到的用例。例如,如果所有成员都丢失,我认为 mypy 报告“此对象未在协议中实现任何内容”错误消息而不是更通用的错误消息是合理的,并且可能会处理存在的情况太多的失踪成员更优雅。如果您愿意,可以尝试提交 PR 以改进这些启发式方法?

如果您没有时间,您可以尝试获取完整列表的一种解决方法是:

  1. 确保 Frobbable 的所有成员都是抽象的(您已经这样做了)
  2. 使 BrokenKnob 暂时成为 Frobbable 的子类
  3. 尝试临时创建一个新的 BrokenKnob 实例。

由此产生的错误似乎列出了所有缺少的属性而没有固定限制。

error: Cannot instantiate abstract class 'BrokenKnob' with abstract attribute 'frob'

然后,完成修复后,您可以撤消临时更改。

【讨论】:

  • 看代码,好像是only reports up to MAX_ITEMS missing protocol members;不仅如此,它不会打扰列出任何东西。 MAX_ITEMScurrently hard-coded to 2
  • @chepner -- 好点!看起来这实际上可能不是一个错误,在再看一遍之后,更多的是一个有意的限制/可能的增强区域。我会更新我的答案。
  • 它看起来确实是一个相当 low 的限制 :) 太糟糕了,它是一个本地函数,而不是 MessageBuilder 的类属性,因此它很容易被覆盖。
猜你喜欢
  • 2017-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多