确定特定对象为何未实现协议

Determining why a particular object does not implement a Protocol

假设我定义了一个协议 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.

的目的

我认为这是由于 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 报告 "this object doesn't implement anything in the protocol" 错误消息而不是更通用的错误消息是合理的,并且可能更优雅地处理缺失成员太多的情况.如果您愿意,可以尝试提交 PR 来改进这些启发式方法?

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

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

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

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

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