Python 中的接口,一文讲清

在应用开发背景下,接口是一种抽象合约,它指定了一组方法和/或属性,换句话说,就是一个组件必须执行的操作,而不定义该功能的实现方式。目的是确保只要不同的实现遵循一样的合约,它们就是可互换的。

Python 中的接口,一文讲清

主要优势

  • 一致性: 强制执行统一的 API。
  • 解耦: 允许更改实现而不影响消费者。
  • 可测试性: 使得基于契约使用模拟或存根变得容易。

在 Python 中,”接口”的概念不是一个独立的语法实体(与 Java 或 C#中的保留关键字 interface 不同),但它可以通过抽象基类(ABCs)或协议(PEP 544)实现——并且推荐这样做。

下面是一个清晰、简单的例子。

Python 中使用abc的接口示例:

pyfrom abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius
    def area(self) -> float:
        return 3.1416 * (self.radius ** 2)

class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height
    def area(self) -> float:
        return self.width * self.height

class Triangle(Shape):
    def __init__(self, base: float, height: float):
        self.base = base
        self.height = height
    def area(self) -> float:
        return (self.base * self.height) / 2

def total_area(shapes: list[Shape]) -> float:
    return sum(shape.area() for shape in shapes)

shapes = [Circle(3), Rectangle(4, 5)]
print(total_area(shapes))

shapes.append(Triangle(6, 4))
print(total_area(shapes))

这个接口是如何工作的

  • Shape 是一个定义为抽象类 (ABC) 的接口,它强制所有子类实现 area() 方法。
  • 通过将 area() 声明为 @abstractmethod,任何没有实现它的子类都不能被实例化。
  • 由于这个接口,total_area 函数不需要知道它接收到的具体形状类型;它只需对每个对象调用 area() —— 这就是多态。
  • 您可以在不修改接口或总面积的情况下添加新的形状(例如, 三角形 ),使代码易于扩展但难以修改。

使用协议的示例:

from typing import Protocol

class Shape(Protocol):
    def area(self) -> float:
        ...
class Circle:
    def __init__(self, radius: float) -> None:
        self.radius = radius
    def area(self) -> float:
        return 3.1416 * (self.radius ** 2)
class Rectangle:
    def __init__(self, width: float, height: float) -> None:
        self.width = width
        self.height = height
    def area(self) -> float:
        return self.width * self.height
class Triangle:
    def __init__(self, base: float, height: float) -> None:
        self.base = base
        self.height = height
    def area(self) -> float:
        return (self.base * self.height) / 2
def total_area(shapes: list[Shape]) -> float:
    return sum(shape.area() for shape in shapes)

shapes = [Circle(3), Rectangle(4, 5)]
print(total_area(shapes))

shapes.append(Triangle(6, 4))
print(total_area(shapes))

接口如何与协议协同工作

形状是一个协议,定义了一个结构化接口(鸭子类型):任何实现具有一样签名的 area() 方法的类都会自动满足该协议,无需显式继承。

共享一样的签名意味着三件事:

  1. 方法名称( 面积
  2. 参数(仅 self,无额外参数)
  3. 返回类型( 浮点数或兼容的数值类型)

如果只有名称匹配但参数或返回类型不同,则协议合同未得到履行。

没有抽象方法或继承要求;只需具有一样的方法签名即可使类型兼容。

total_area 函数接受任何满足 Shape 协议的对象,利用灵活的多态性。

添加新形状(例如,五边形)只需实现面积(),无需修改协议或总面积函数,满足 SOLID 开/闭原则(OCP)。

ABC 与协议

使用抽象基类(ABCs)和协议之间有关键区别,尽管这两种方法都允许你定义合同以确保类实现一个 area() 方法。

协议基于鸭子类型,依赖于结构化类型。这意味着任何实现了具有正确签名的 area() 方法的类,无需继承它即可自动满足协议。它们被认为更现代、更优雅。它们可以自然地与 mypy 等类型检查工具集成,使应用程序设计中的类型验证更加灵活和侵入性更低。

通过协议,您可以避免与继承和抽象方法的强制实现相关的代码重复。只需依靠方法签名,就可以减少“样板”代码,并简化类的结构。这在需要保持代码整洁并避免冗余的项目中可以是一个显著的优势。

提议在从结构化类型和不需要显式继承的灵活性中受益的项目中使用协议。如果您使用 mypy,这个选项尤其推荐,由于它允许更平滑、更灵活的集成,使代码更容易扩展和维护。

ABCs 需要从抽象类(ABC)继承,并显式声明抽象方法。这迫使子类实现定义的方法,这在需要严格结构时超级有用。

ABCs 更传统,可能更冗长,由于它们需要显式继承和使用装饰器来标记抽象方法。当需要通过继承建立明确、强制性的契约时,使用 ABCs 是合适的,尤其是在类层次结构定义良好且结构必须严格的情况下。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
夏天被通缉的头像 - 宋马
评论 共1条

请登录后发表评论