Python 中的接口

2024 年 8 月 29 日 | 阅读 6 分钟

在本教程中,我们将学习如何在 Python 中实现接口。通常,接口不是 Python 的一部分,但我们可以使用 ABC 模块来实现它。我们将了解接口的工作原理以及创建 Python 接口的注意事项。

接口在软件工程中起着至关重要的作用,因为它们提供了一种定义一组方法或行为的方式,而类必须实现这些方法或行为。随着应用程序的规模和复杂性不断增长,管理代码库的更新和更改变得越来越困难。

出现的一个常见问题是存在具有相似功能但继承层次结构不相关的类。这可能导致代码库中的混淆和冗余。

与大多数其他编程语言相比,Python 在接口方面有着独特的方法,并且接口在 Python 中的设计复杂性可能会有所不同。完成本教程后,您将对 Python 的数据模型以及 Python 接口与 Java、C++ 和 Go 等语言的接口之间的区别有更深入的了解。

接口简介

接口是用于创建具有共同方法的类的设计模板。接口中定义的方法是抽象的,这意味着它们仅被勾勒出来而没有实现。这与类不同,类为其方法提供了具体的实现。相反,实现接口的类将填充抽象方法,赋予它们特定的含义和功能。

通过定义接口,开发人员可以创建一个标准契约,类可以遵循该契约,而不管它们的继承层次结构如何。这允许更好的代码组织,并减少代码重复的可能性。此外,接口可以提供一种解耦应用程序组件的方式,从而更容易对一个组件进行更改或更新,而不会影响其他组件。

总而言之,接口是管理软件工程复杂性的强大工具,它们的使用可以带来更易于维护和可扩展的代码库。

Python 在实现接口方面遵循与 C++、Java 和 Go 等其他语言不同的方法。这些语言具有接口关键字,而 Python 没有这样的关键字。Python 在另一个方面也不同于其他语言。它不需要类来实现接口来定义其所有抽象方法。

非正式接口

众所周知,Python 是一种动态语言,允许我们实现对话式接口。非正式接口是定义可以覆盖的方法的类,但没有严格的强制执行。

在下面的示例中,将创建一个非正式接口来从各种非结构化文件格式(包括 PDF 和电子邮件)中提取文本。该接口将定义在 PdfParserEmlParser 具体类中实现的必要方法。

示例 -

TextExtractor 类定义了 .load_data_source() 和 .extract_text() 方法,其中第一个方法 load_data_source 接受两个参数:path 和 file_name,两者都是 str 类型。此方法负责加载位于给定路径下的指定文件名的文件。方法签名表明该方法应返回 str 值。

第二个方法 extract_text 接受一个参数:full_file_name,它也是 str 类型。此方法负责从 full_file_name 指定的文件中提取文本,该文件已加载到类实例中。方法签名表明该方法应返回 dict 值。

正如您所注意到的,我们仅定义了这些方法而没有实现。当我们创建继承自 InformalParserInterface 的具体类时,将实现这些方法。

现在,我们将定义实现 InformalParserInterface 的两个类。要使用它,我们必须实现具体类。具体类必须继承我们提供接口方法实现的接口。

示例 -

通过实现 InformalParserInterface 的具体类,现在可以从电子邮件文件中提取文本。但是,EmlParser 定义了 .extract_email_text() 而不是 .extract_email()。现在,我们检查 PdfParser 和 EmlParser 是否实现了 TextExtractor。让我们看下面的示例。

正如我们所看到的,第二个类返回 true,这违反了接口的定义。

使用元类

期望的结果是当实现类未定义接口的所有抽象方法时,issubclass(EmlParser, TextExtract) 返回 False。为了实现这一点,将创建一个名为 ParserMeta 的元类,并覆盖两个特殊方法(dunder 方法):

  1. .__instancecheck__()
  2. .__subclasscheck__()

在下面的代码块中,您将创建一个名为 UpdatedInformalParserInterface 的类,该类基于 ParserMeta 元类构建。

示例 -

现在,我们可以创建具体实现。

现在我们实现名为 EmlParserNew 的电子邮件解析器。

在此代码片段中,名为 ParserMeta 的元类用于创建 UpdatedInformalParserInterface 接口。使用元类的优点是无需显式定义子类。相反,子类需要定义必要的方法。如果子类未定义这些方法,则 issubclass(EmlParserNew, UpdatedInformalParserInterface) 将返回 False。

使用抽象方法声明

抽象方法与类一起定义,但未实现。这些方法必须被继承具有抽象方法的类的类覆盖。

要定义抽象方法,我们需要将 @abc.abstractmethod 装饰器应用于接口的方法。让我们来理解下面的例子。

示例 -

在此示例中,我们定义了一个名为 MyInterface 的接口,它包含一个抽象方法 my_method()。我们使用 @abc.abstractmethod 装饰器将 my_method 标记为抽象方法。任何继承自 MyInterface 的类都必须为 my_method 提供实现。

然后,我们定义了一个名为 MyClass 的类,通过为 my_method 提供实现来实现 MyInterface。当我们创建一个 MyClass 实例并调用 my_method() 时,它将执行 MyClass 提供的实现。

假设我们创建了一个 MyInterface 的子类,但没有为 my_method 提供实现。在这种情况下,我们将在运行时收到一个 TypeError,表明子类未被视为接口的完整实现。

结论

本教程详细解释了 Python 中的接口以及何时需要它们。尽管 Python 没有接口关键字,但我们可以通过多种方式定义它。Python 在创建接口时提供了极大的灵活性。对于小型项目,非正式的 Python 接口可能很有用,您可能不需要担心方法的返回类型。然而,随着项目的增长,正式的 Python 接口变得更加重要,以确保实现接口的具体类覆盖了抽象方法。

在 Python 中,您可以使用 ABC 模块创建标准接口,该模块提供了一种定义抽象类和方法的方法。您可以使用 @abc.abstractmethod 装饰器将方法标记为抽象的,任何继承自抽象类的具体类都必须为抽象方法提供实现。