我可以使用 Mypy 存根作为接口吗?

Hub*_*iak 4 python mypy

Mypy 允许我们编写类存根,可以将其放置在与实际类相同的目录中。该存根与其他语言中已知的接口非常相似。是否可以让客户端使用存根并严格遵循存根实现?

我想工作的例子:

class IDependency:
  def do_something(self) -> None: ...
  def do_something_else(self) -> None: ...

class Service:
  def __init__(self, dependency: IDependency):
    dependency.do_something()
    dependency.do_something_else() # this fails silently

class DependencyImplementation(IDependency):
  def do_something(self) -> None:
    print("doing something")

  # Note there is no `do_something_else` here.
Run Code Online (Sandbox Code Playgroud)

这有效。但是,如果DependencyImplementation不实现该do_something方法,则 Mypy 不会出现错误,Python 本身也不会出现错误。调用只是没有执行任何操作。我是否必须编写raise NotImplementedException()或注释每个方法@abc.abstractmethod才能使其工作?Mypy 或 Python 解释器中是否有一些特殊标志?

这是Mypy 协议的用例吗?它似乎即将推出(也许是 Python 4?)

Mic*_*x2a 5

这确实可以使用@abc.abstractmethod或 协议来完成。前者类似于使用Java的抽象类;后者类似于使用 Go 的接口或 Rust 特征。

下面是一个使用 ABC 的示例:

from abc import abstractmethod

class Parent:
    @abstractmethod
    def foo(self) -> None: ...

# Missing an implementation for 'foo'!
class Child(Parent): pass

print(Child())  # Error: Cannot instantiate abstract class 'Child' with abstract attribute 'foo'
Run Code Online (Sandbox Code Playgroud)

关于这个例子有几点需要注意:

  1. 您会在Child 类的实例化过程中遇到错误,而不是在声明过程中出现错误。这是为了支持这样的用例:您从不实例化 Child,而是再次对其进行子类化并foo在第二个子类中进行定义。
  2. 我们不需要abc向 Parent 添加通常的元类(例如class Parent(metaclass=ABCMeta)):mypy 会理解@abc.abstractmethod有或没有它的含义。仅当您希望 Python 解释器也强制您已正确覆盖运行时标记为抽象的任何内容时,才包含元类。
  3. ABC 并不完全是接口——您仍然可以定义字段和非抽象方法。它们更类似于 Java 风格的抽象类。

您还可以使用协议,但现在您需要首先pip install typing_extensions使用它。这是一个例子:

from typing_extensions import Protocol

class CanFoo(Protocol):
    def foo(self) -> None: ...

class Child: pass

def expects_fooable(x: CanFoo) -> None: ...

x = Child()
expects_fooable(x)  # Error: Argument 1 to "expects_fooable" has incompatible type "Child"; expected "CanFoo"
Run Code Online (Sandbox Code Playgroud)

一些注意事项:

  1. 这里,Child故意不继承自CanFoo:类与其实现的协议之间没有显式链接:协议与 Go 风格的接口非常相似,并且可以更加临时。将此与 Java 等语言进行对比,在 Java 中,您确实需要在类定义中包含“implements Blah”。
  2. 与之前的错误不同,我们在实例化 时不会收到错误Child:它本身没有任何问题。相反,当我们尝试不正确地使用它时,我们会遇到异常。

最后一些注意事项:

  1. 存根文件可能表面上看起来像接口,但实际上并非如此:它们更多地只是将类型引入代码的一种方式,我们无法轻松地对其进行修改和添加类型提示。您可以认为它们与 C 风格的头文件有些相似:它是一种独立于源代码存储现有对象签名的方法。
  2. “Typeshed”是一个特定项目的名称,其中包括标准库的存根和一些流行的第三方模块。该词不是“存根文件”的同义词。同样,术语“类存根”也有点用词不当:只有存根文件,其中可能包含也可能不包含类的定义。(如果您尝试输入的原始 Python 或 C 扩展库仅包含函数,则相应的存根文件也可能仅包含这些函数的签名。)