使用常规方法和类方法实现 Python 协议

Mat*_*bin 22 python type-hinting python-3.x

假设我有两个类Foo1并且Foo2实现了一个方法bar()

  • Foo1,bar()是一个正则方法
  • Foo2bar()是一个@classmethod
class Foo1:

    def bar(self) -> None:
        print("foo1.bar")


class Foo2:

    @classmethod
    def bar(cls) -> None:
        print("Foo2.bar")
Run Code Online (Sandbox Code Playgroud)

现在假设我有一个函数,它接受“任何有bar()方法的东西”列表并调用它:

def foreach_foo_call_bar(foos):
    for foo in foos:
        foo.bar()
Run Code Online (Sandbox Code Playgroud)

在运行时调用此函数工作正常:

foreach_foo_call_bar([Foo1(), Foo2])
Run Code Online (Sandbox Code Playgroud)

因为两者都有Foo1()并且Foo2有一个bar()方法。

但是,如何正确地将类型提示添加到foreach_foo_call_bar()?

我尝试创建一个名为的PEP544ProtocolSupportsBar

class SupportsBar(Protocol):

    def bar(self) -> None:
        pass
Run Code Online (Sandbox Code Playgroud)

并像这样注释:

def foreach_foo_call_bar(foos: Iterable[SupportsBar]):
   ...
Run Code Online (Sandbox Code Playgroud)

但是 mypy 说:

List item 1 has incompatible type "Type[Foo2]"; expected "SupportsBar"
Run Code Online (Sandbox Code Playgroud)

知道如何正确注释吗?

che*_*ner 6

问题似乎Protocol是专门检查是否 支持实例方法,而不仅仅是存在正确名称的属性。在 的情况下Foo2,这意味着元类需要一个名为 的实例方法bar;以下似乎按预期行为和类型检查。

# Define a metaclass that provides an instance method bar for its instances.
# A metaclass instance method is almost equivalent to a class method.
class Barrable(type):
    def bar(cls) -> None:
        print(cls.__name__ + ".bar")


class Foo1:
    def bar(self) -> None:
        print("foo1.bar")


class Foo2(metaclass=Barrable):
    pass


class SupportsBar(Protocol):
    def bar(self) -> None:
        pass


def foreach_foo_call_bar(foos: Iterable[SupportsBar]):
    for foo in foos:
        foo.bar()
Run Code Online (Sandbox Code Playgroud)

我不会声称将类方法转换为元类实例方法是一个很好的解决方法(实际上,它对静态方法没有任何作用),但它指出这是Protocol它不处理任意属性的基本限制。


Eva*_*rim 6

这是mypy 的一个悬而未决的问题。BFDL 本人甚至在@MatanRubin 提出这个问题前两年就承认这是不正确的行为。它仍未解决,但最近(2019 年 11 月)被标记为高优先级,因此希望此处提供的示例不会很快再产生误报。


use*_*257 5

在列表中的第二项foosFoo2是一个类不是一个实例如Foo1()。这需要通过环绕来指定Type

def foreach_foo_call_bar(
        foos: Iterable[Union[SupportsBar,
                             Type[SupportsBar]]]):
    # ...
Run Code Online (Sandbox Code Playgroud)

您也可以在不使用 a 的情况下执行此操作Protocol

def foreach_foo_call_bar(
        foos: Iterable[Union[Foo1,
                             Type[Foo2]]]):
    # ...
Run Code Online (Sandbox Code Playgroud)

  • 我也想过,但好像很麻烦。我正在寻找一种基于可用方法来定义类型的方法,无论这些方法是类方法还是实例方法。这是经典的鸭子类型 - 我不关心对象是什么,我只关心它有一个名为“bar()”的方法。IOW,我试图找到一种方法让类型检查器知道某些东西“像鸭子一样嘎嘎叫”,而不关心它是一个类还是一个实例。如果在运行时我可以在该对象上调用“bar()”,那么我正在寻找的类型应该满足类型检查器的要求,而不需要联合。 (3认同)