(十一)用抽象基类与协议写清接口
1. 抽象基类要求子类完成指定操作
开发一组同类来源时,可以显式给出一个基类,并把必须实现的方法标成抽象方法:
# ABC 建立抽象基类,abstractmethod 标出需要由具体类完成的方法。
from abc import ABC, abstractmethod
class TitleSource(ABC):
@abstractmethod
def load(self) -> list[str]:
raise NotImplementedError
class MemorySource(TitleSource):
def load(self) -> list[str]:
return ["Python", "NumPy"]
try:
TitleSource()
except TypeError:
print("load 尚未实现")
print(MemorySource().load())输出“load 尚未实现”和 ['Python', 'NumPy']。抽象方法仍未完成的类,在创建实例时就会被报告为错误。NotImplementedError 在方法体中表示这项处理尚未提供;这里对抽象类的创建检查发生得更早,不会先进入这个方法体。
抽象基类可以同时提供公共实现与抽象接口;继承它的具体类完成抽象方法后,再用公共处理。抽象标记要求实现存在,参数与返回数据的具体含义仍由接口说明和实际调用共同核对。抽象基类
2. Protocol 描述结构,不强制继承同一父类
上一章的类型注解也能描述“这个对象提供哪些方法”。下面的协议写明来源提供 load() -> list[str]:
# Protocol 让类型工具按对象提供的方法结构检查协作关系。
from typing import Protocol
class TitleSource(Protocol):
def load(self) -> list[str]:
... # 省略号在这里作为接口方法体的占位,不提供实际读取行为。
class MemorySource:
def load(self) -> list[str]:
return ["Python", "NumPy"]
def total_titles(source: TitleSource) -> int:
return len(source.load())
print(total_titles(MemorySource()))输出 2。MemorySource 没有继承 TitleSource,但它的方法形状符合这份协议。普通 Python 运行仍然按 source.load() 调用;协议注解供编辑器与类型检查工具分析,不会自动替函数检查所有输入与返回结果。结构化协议
| 安排方式 | 这份示例中明确表达的内容 |
|---|---|
| 直接使用鸭子类型 | 调用者使用 load(),由实际对象提供实现 |
| 抽象基类 | 明确建立继承关系,抽象方法未完成时阻止实例化 |
Protocol |
在注解中写出调用者需要的方法结构 |
综合书架使用协议描述存储器,再通过组合传入具体实现:接口形状与状态所在的位置分别写清楚。