谈谈Python中的接口与抽象基类
接触Python比较早的朋友可能都有这样的体会,Python语言虽然也支持面向对象的编程方式,但是,不像那些纯面向对象的语言(好比Java和.NET)那样严格和规范。
随着项目的规模逐步扩大之后,想要以一种清晰、可维护和可扩展的方式定义和实行对象的行为就变得越来越困难。
本日介绍的Python中两个为面向对象编程提供的强大工具:接口和抽象基类。
它们的英文分别为Protocols和ABC(Abstract Base Classes)。
Protocols是Python3.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_data的class,好比APIHandler,CSVHandler和SqliteHandler,都可以当做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.com3. 两者的选择
当我们在现实的开发设计中,应该如何选择Protocol和ABC呢?
实在,Protocol和ABC之间的选择并不是非黑即白,这通常取决于项目的配景和你的目标。
一般来说,下面这些情况我们优先选择使用Protocol:
[*]你正在使用现有代码或希望集成第三方库
[*]灵活性是首要使命,你不想逼迫执行严格的条理结构
[*]来自不相关的类条理结构的对象需要共享行为
而下面这些情况,优先选择ABC:
[*]正在从头开始设计一个系统,而且需要加强结构
[*]类之间的关系是可预测的,而且继承是有意义的
[*]共享功能或默认行为可以减少重复并提高一致性
4. 总结
总的来说,Protocol和ABC不是互相竞争的两种工具,它们是互补的。
我使用Protocol将类型安全改造到遗留系统中,而不需要大量重构。
另一方面,如果我在从头开始构建一个结构和一致性至关紧张的系统时,会使用 ABC。
在决定使用哪个时,请考虑项目的灵活性需求和长期目标。
Protocol提供灵活性和无缝集成,而 ABC 有助于建立结构和一致性。
通过了解它们各自的优势,你可以选择合适的方式来构建结实、可维护的 Python 系统。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]