接触Python比较早的朋友可能都有这样的体会,Python语言虽然也支持面向对象的编程方式,

但是,不像那些纯面向对象的语言(比如Java.NET)那样严格和规范。

随着项目的规模逐步扩大之后,想要以一种清晰、可维护和可扩展的方式定义和实施对象的行为就变得越来越困难。

今天介绍的Python中两个为面向对象编程提供的强大工具:接口抽象基类

它们的英文分别为ProtocolsABC(Abstract Base Classes)。

ProtocolsPython3.8才开始引入的,有的地方也翻译成协议,我感觉翻译成接口更熟悉一些。

ABC引入的比较早,在Python3之后得到了改进和优化,现在和其他语言的抽象类相比,差别不大。

1. 接口(Protocols)

Python3.8开始在类型模块中引入的接口Protocols的概念,它提供了一种无需显式继承即可定义接口的方法。

接口Protocols定义了一组方法或属性,只要一个对象实现了这些方法或属性,就被视为满足该接口。

下面通过一个示例来帮助理解Protocols的使用,如果有面向对象编程的经验,很容易就能理解这个概念。

这个示例来自最近用的一个量化交易系统的一部分,这个功能需要从不同的来源获取数据,然后进行分析,最后将分析结果以不同的方式输出

这三个步骤(获取数据,分析和输出)中,

假设获取数据的来源有网络(API),文件(CSV)和数据库3种;

分析的步骤是统一的;输出的方式假设也有多种,比如邮件,短信等等。

根据这个描述,使用Protocols构建的获取数据和分析部分的代码如下:

输出的部分暂时不管

from typing import Protocol

# 输入数据的接口
class InputData(Protocol):
def get_data(self) -> str:
pass class APIHandler:
def get_data(self) -> str:
print("get_data from API")
return "get data from API" class CSVHandler:
def get_data(self) -> str:
print("get_data from CSV")
return "get data from CSV" class SqliteHandler:
def get_data(self) -> str:
print("get_data from SQLITE DATABASE")
return "get data from SQLITE DATABASE" # 分析数据
def analysis(i: InputData):
data = i.get_data() print("开始处理数据...")

InputData继承了Protocol,其中定义了接口的函数get_data

只要实现了get_dataclass,比如APIHandlerCSVHandlerSqliteHandler,都可以当做InputData类型。

从代码可以看出,我们不需要用APIHandler去继承InputData,只要实现InputData中的所有方法就可以了。

这种灵活性确保了系统的可扩展性,我们可以添加新的数据源类型,而无需修改现有代码。

接下来我们测试上面的代码是否可以正常使用:

if __name__ == "__main__":
i = APIHandler()
analysis(i)
print("\n") i = CSVHandler()
analysis(i)
print("\n") i = SqliteHandler()
analysis(i)
print("\n")

运行结果:

$  python.exe .\protocol_abc.py
get_data from API
开始处理数据... get_data from CSV
开始处理数据... get_data from SQLITE DATABASE
开始处理数据...

2. 抽象基类(ABC)

Protocol非常具有灵活性,但有时我们需要更结构化的方法,这就是抽象基类 (ABC) 的用武之地。

ABC 是一种通过定义子类必须实现的严格接口来强制执行一致行为的工具。

Protocol不同,ABC 需要显式继承,因此当我们希望在代码中明确定义层次结构时,ABC是更好的选择。

接着实现上一节示例中的输出部分,每种不同的输出需要不同的配置,

比如输出到邮件需要先配置邮箱的账号信息,输出到短信需要配置手机信息等等。

在这里,我们使用 ABC 来实现输出的基类。

# 输出的抽象基类
class OutputResult(ABC):
def __init__(self):
self.settings: dict = {} @abstractmethod
def send(self, data: str):
pass @abstractmethod
def config(self, settings: dict):
pass class OutputMail(ABC):
def send(self, data: str):
print(f"send {data} to {self.settings['name']}") def config(self, settings: dict):
self.settings = settings class OutputMessage(ABC):
def send(self, data: str):
print(f"send {data} to {self.settings['name']}") def config(self, settings: dict):
self.settings = settings

这里使用抽象基类的原因是输出时,并不是简单的调用send方法就可以的,还需要配置输出的参数,

所以用带有结构的抽象基类更好。

加上输出之后,上一节中的分析函数也改为:

# 分析数据
def analysis(i: InputData, o: OutputResult):
data = i.get_data() print("开始处理数据...")
data = data.replace("get data from ", "") o.send(data)

测试的代码如下:

if __name__ == "__main__":
i = APIHandler()
o = OutputMail()
o.config({"name": "aaa@bbb.com"})
analysis(i, o)
print("\n") i = CSVHandler()
o = OutputMessage()
o.config({"name": "13911123456"})
analysis(i, o)
print("\n") i = SqliteHandler()
o = OutputMail()
o.config({"name": "xyz@www.com"})
analysis(i, o)
print("\n")

运行结果:

$  python.exe .\protocol_abc.py
get_data from API
开始处理数据...
send API to aaa@bbb.com get_data from CSV
开始处理数据...
send CSV to 13911123456 get_data from SQLITE DATABASE
开始处理数据...
send SQLITE DATABASE to xyz@www.com

3. 两者的选择

当我们在实际的开发设计中,应该如何选择ProtocolABC呢?

其实,ProtocolABC之间的选择并不是非黑即白,这通常取决于项目的背景和你的目标。

一般来说,下面这些情况我们优先选择使用Protocol

  • 你正在使用现有代码或希望集成第三方库
  • 灵活性是首要任务,你不想强制执行严格的层次结构
  • 来自不相关的类层次结构的对象需要共享行为

而下面这些情况,优先选择ABC

  • 正在从头开始设计一个系统,并且需要加强结构
  • 类之间的关系是可预测的,并且继承是有意义的
  • 共享功能或默认行为可以减少重复并提高一致性

4. 总结

总的来说,ProtocolABC不是互相竞争的两种工具,它们是互补的。

我使用Protocol将类型安全改造到遗留系统中,而不需要大量重构。

另一方面,如果我在从头开始构建一个结构和一致性至关重要的系统时,会使用 ABC

在决定使用哪个时,请考虑项目的灵活性需求和长期目标。

Protocol提供灵活性和无缝集成,而 ABC 有助于建立结构和一致性。

通过了解它们各自的优势,你可以选择合适的方式来构建健壮、可维护的 Python 系统。

谈谈Python中的接口与抽象基类的更多相关文章

  1. python 用abc模块构建抽象基类Abstract Base Classes

    见代码: #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/08/01 16:58 from abc import ABCMet ...

  2. NSObject class和NSObject protocol的关系(抽象基类与协议)

    [转载请注明出处] 1.接口的实现 对于接口这一概念的支持,不同语言的实现形式不同.Java中,由于不支持多重继承,因此提供了一个Interface关键词.而在C++中,通常是通过定义抽象基类的方式来 ...

  3. Fluent_Python_Part4面向对象,11-iface-abc,协议(接口),抽象基类

    第四部分第11章,接口:从协议到抽象基类(重点讲抽象基类) 接口就是实现特定角色的方法集合. 严格来说,协议是非正式的接口(只由文档约束),正式接口会施加限制(抽象基类对接口一致性的强制). 在Pyt ...

  4. 4.6 C++抽象基类和纯虚成员函数

    参考:http://www.weixueyuan.net/view/6376.html 总结: 在C++中,可以通过抽象基类来实现公共接口 纯虚成员函数没有函数体,只有函数声明,在纯虚函数声明结尾加上 ...

  5. spring aspect获取抽象基类日志

    在实际的项目开发过程中我们其实封装了很多的类似BaseService.BaseDao等的基类,然后在切日志的时候我们一般是指向继承改抽象基类的实现类的,这时候我们就会出现无法切出调用抽象基类方法的日志 ...

  6. Python 接口:从协议到抽象基类

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } 抽象基类的常见用途:实现接口时作为超类使用.然后,说明抽象基类如何检查 ...

  7. Python中的对象行为与特殊方法(二)类型检查与抽象基类

    类型检查 创建类的实例时,该实例的类型为类本身: class Foo(object): pass f = Foo() 要测试实例是否属于某个类,可以使用type()内置函数: >>> ...

  8. Python中的抽象基类

    1.说在前头 "抽象基类"这个词可能听着比较"深奥",其实"基类"就是"父类","抽象"就是&quo ...

  9. 流畅python学习笔记:第十一章:抽象基类

    __getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克 ...

  10. guxh的python笔记七:抽象基类

    1,鸭子类型和白鹅类型 1.1,白鹅类型 白鹅类型对接口有明确定义,比如不可变序列(Sequence),需要实现__contains__,__iter__,__len__,__getitem__,__ ...

随机推荐

  1. 使用Navicat Premium 将数据库导入、导出方法

    数据库导出 1.双击要导出的数据库,右键选转储SQL文件-,选择要保存的文件夹. 2.点击开始后,开始导出. 数据库导入 1.新建数据库,数据库的名字必须和导入的数据库文件一致. 2.在新建的数据库右 ...

  2. 利用AutoGpt将任何模型支持o1模型的推理实现

    利用AutoGpt将任何模型支持o1模型的推理实现 相信大家都对于OpenAI最新出的o1模型都非常关注,它已经能通过推理让回复的效果更加理想, 但是目前o1的限制太大,而且使用o1至少也是需要购买O ...

  3. SpringMVC —— 请求参数

    请求映射路径    请求方式   get请求传参        post请求传参          POST请求中文参数乱码问题        请求参数(五种类型数据参数)               ...

  4. PS安装插件提示无法加载扩展未正确签署 的解决办法

    PS安装插件提示无法加载扩展未正确签署解决方式 win系统: 1.打开"运行"窗口(点击电脑左下角"开始"菜单,从打开的菜单中依次点击"所有程序&qu ...

  5. Web前端技术丛书代码下载

    我是清华社编辑,这些下载资源供读者个人学习使用,禁止商用. IE/Chrome下载,或者微信扫描二维码,按提示发邮箱下载. 二维码用微信扫码,可填写邮箱,把链接转发邮箱下载. <微信小程序开发从 ...

  6. 【题目全解】ACGO排位赛#13

    ACGO排位赛#13 - 题目解析 感谢大家参加本次排位赛! T1 - 纪元流星雨 题目链接跳转:点击跳转 也没有特别大的难度,手动模拟一下就可以了. 解题步骤 先计算出这个人一生中第一次看到流星雨的 ...

  7. Ubuntu 22.04 解决和 Windows 共享蓝牙设备的问题

    我有一个 Airpods,连接到 WIndows 可以正常工作,但连接到 ubuntu 后会无法连接,只能删除设备选择重联,但是这又会导致 Windows 不能连接到耳机,只能也删除重新连接,费神费力 ...

  8. Iterator和Iterable

    Java遍历List有三种方式 public static void main(String[] args) { List<String> list = new ArrayList< ...

  9. 轻量级网络-MobileNetv1 论文解读

    1.相关工作 标准卷积 分组卷积 从 Inception module 到 depthwise separable convolutions 2.MobileNets 结构 2.1,深度可分离卷积 D ...

  10. mysql隐蔽的索引规则导致数据全表扫描

    索引是为了加速数据的检索,但是不合理的表结构或适应不当则会起到反作用.我们在项目中就遇到过类似的问题,两个十万级别的数据表,在做连接查询的时候,查询时间达到了7000多秒还没有查出结果. 首先说明,关 ...