假设我有两个类Foo1
和Foo2
来实现方法bar()
:
- 在
Foo1
中,bar()
是一个常规方法 - 在
Foo2
,bar()
是一个@classmethod
class Foo1:
def bar(self) -> None:
print("foo1.bar")
class Foo2:
@classmethod
def bar(cls) -> None:
print("Foo2.bar")
现在假设我有一个函数,它接受"任何具有bar()
方法"的列表并调用它:
def foreach_foo_call_bar(foos):
for foo in foos:
foo.bar()
在运行时调用此函数可以正常工作:
foreach_foo_call_bar([Foo1(), Foo2])
因为Foo1()
和Foo2
都有bar()
方法。
但是,如何正确向foreach_foo_call_bar()
添加类型提示?
我尝试创建一个名为SupportsBar
的 PEP544Protocol
:
class SupportsBar(Protocol):
def bar(self) -> None:
pass
并像这样注释:
def foreach_foo_call_bar(foos: Iterable[SupportsBar]):
...
但mypy说:
List item 1 has incompatible type "Type[Foo2]"; expected "SupportsBar"
知道如何正确注释吗?
问题似乎是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()
我不会声称将类方法转换为元类实例方法是一个很好的解决方法(实际上,它对静态方法没有任何作用(,但它指出这是Protocol
它不处理任意属性的基本限制。
这是mypy的一个开放问题。 BFDL自己甚至在@MatanRubin提出这个问题的2年前就承认这是不正确的行为。 它仍未解决,但最近(2019 年 11 月(被标记为高优先级,因此希望此处提供的示例不会很快产生误报。
foos
列表中的第二项,Foo2
是一个类而不是像Foo1()
这样的实例。这需要通过环绕Type
来指定:
def foreach_foo_call_bar(
foos: Iterable[Union[SupportsBar,
Type[SupportsBar]]]):
# ...
您也可以在不使用Protocol
的情况下执行此操作:
def foreach_foo_call_bar(
foos: Iterable[Union[Foo1,
Type[Foo2]]]):
# ...