Python中的继承、抽象基类和接口 Datawhale学习笔记【阿里云天池 金融风控-贷款违约预测】task1 赛题理解 Pytorch - torchvision计算机视觉工具库 linux 重点笔记 Ubuntu18.04安装ROS Melodic(一路到站型) 小甲鱼笔记:数据结构——线性表(一)线性表的顺序存储结构,线性表顺序存储结构的增,删,插入元素操作 实战比特币脚本编程(1) JAVA WEB DAY 01_Tomcat & Servlet Java基础算法之堆排序(Heap Sort) synchronized批量重偏向与批量撤销 终于等到了!阿里P8历时九个月整理,Java面试宝典,核心知识点笔记在此 “数字心脏”动态解析消费密码,国家级消费市场大数据联合实验室在上海先行先试 全世界运行着大约230亿台物联网设备,安全问题如何解? 物联网产业园&thinkplus解决方案中心国学讲座如期而至 都是程序员,凭什么他能站在鄙视链的顶端? 猛男必看!去小红书做程序员是种什么体验 drozer提示[Errno 2] No such file or directory 【STM32】NB-iOT BC35-G模块 AT指令应用设计指导(附代码) 【北京迅为】i.MX6ULL终结者编译LED汇编程序 Linux系统读写网卡PHY寄存器工具 洛谷:P1226 【模板】快速幂||取余运算(分治,数学) 【2020顶会KDD】AutoST:面向时空预测的高效神经网络学习模型 C/C++实现并查集disjoint_set的模板(带路径压缩优化) 实现一个百万级推送服务,除了它,还有谁 “健康守护者”——STM32标准库和HAL库的比较 程序员被公司辞退12天后,前领导要求回公司讲清代码,结果懵了 RTTR实现C++反射(1)集成rttr库 lotus node 远程运行 CCF历年4,5题收录 N卡 RTX3070/RTX3080/RTX3090挖矿算力推测 最高算力86MH 理论+实验:MySQL索引、事物与存储引擎 PostgreSQL JOIN 多表查询 TP框架实现Excel批量导入数据库数据 MySQL中的列转行 这次是真拯救了我,MySQL索引优化,explain讲得非常清楚了 Oracle快速入门(PLSQL编程) MySQL字符串拼接、截取 MySQL事务管理及存储引擎 《浪姐》万茜点赞宁静、郁可唯黑贴坐实?盗号者和程序员是背锅侠 程序员被公司辞退12天后,前领导要求回公司讲清代码,结果懵了 易语言大漠多线程foobar在游戏多窗口中时时显示输出信息 非科班,自学两年,复盘两个月,侥幸拿到头条、阿里offer 2020年全国数学建模大赛B题源代码以及模型的建立 (2) 2020年数学建模-校园供水系统智能管理 windows破解锁屏密码(亲测有效:再也不怕别人锁屏防你啦!) 清华大佬力荐的JVM学习路线+实战笔记+阿里真题,嚼碎吃透吊打面试官 打造多模块+高可用+高扩展Spring Cloud版分布式电商项目源码分享 Python爬虫入门教程 89-100 定个小目标,先用Python爬个一亿B站用户 五大分布式事务,你了解多少? 2020-09-12
您的位置:首页 >前端 >

Python中的继承、抽象基类和接口

文章目录

一、继承1. 常见数列案例2. 常见数列实现数列基类等差数列等比数列斐波那契数列二、抽象基类三、接口

先一句话总结Python中继承、抽象基类和接口三者之间的关系:Python中的接口机制可通过抽象基类实现,接口的实现有赖于继承机制。

一、继承

继承是面向对象编程语言的三大特性之一(其他两个是封装、多态),所谓继承是指子类自动具有父类所定义的方法和属性,而无需子类再重复定义同名的方法或属性,因此继承的最大优势之一是可以提高代码的复用程度。

1. 常见数列案例

这里以高中数学中一个重要的概念——数列来简介Python的继承概念。数列是一组数值组成的序列,该序列中的每一个值都取决于数列的前一项或多项,例如:

对于等差数列,数列中从第二项开始,每一项都由前一项加上一个固定的常量得到;对于等比数列,数列中从第二项开始,每一项都由前一项乘上一个固定的常量得到;对于斐波那契数列,数列中从第三项开始,每一项都有其前两项之和相加得到。

如果现在需要使用面向对象特性对上述各个不同类型的数列进行代码抽象,则可以想到三个数列类必然都支持下列类似功能的方法:

初始化方法:用于初始化数列的前若干项;遍历支持方法:可以支持以可迭代的方式遍历出数列的项;数列项生成方法:按照一定的规则根据前若干项生成任意项。

如果不采用继承的方式,则最终实现的各数列类必然代码重复度很高。

2. 常见数列实现

针对上述讨论,下面考虑使用继承实现各个数列类:

首先,定义一个通用数列父类Progression,在其中实现数列的共有方法及实用方法;然后,继承Progression类再根据数列通项生成规则分别在等差、等比、斐波那契数列中重写父类方法或定义全新方法。

数列基类

在数列基类中:

__init__初始化方法接收两个参数,start用以指定数列第一项的值,num用以指定默认打印的数列项数,分别用于初始化_current_num的值;_advance方法用于按照数列通项规则生成任意项;__iter____next__方法用以支持Python的迭代器协议(具体请见Python中for循环运行机制探究以及可迭代对象、迭代器详解),用于数列的遍历;__str__方法用于将数列对象转换为列表,并返回该列表的字符串表示形式。
class Progression:"""数列基类"""def __init__(self, start=0, num=10):"""将当前数列的第一项初始化为0:param start: 数列第一项,默认为0:param num: 打印数列时的默认显示项数"""self._current = startself._num = numdef _advance(self):"""用于根据数列前若干项进行任意项的生成,该方法应该被子类重写"""self._current += 1def __next__(self):"""迭代器协议方法,返回数列中的下一项,当已至数列最后一项则抛出StopIteration异常"""if self._num > 0:ans = self._currentself._advance()self._num -= 1return anselse:raise StopIterationdef __iter__(self):"""迭代器协议方法,返回对象自身"""return selfdef __str__(self):"""返回对象的字符串表示形式"""return str(list(self))

等差数列

在等差数列的实现中,由于继承了Progression类,所以:

__init__方法中调用了父类初始化方法,从而对继承自父类的_current以及默认打印的数列项数_num进行了初始化,另外还对该类特有的等差数列常量_increment进行了初始化;_advance方法按照等差数列通项规则对父类同名方法进行了重写;__next____iter____str__方法继承自Progression类,无需重复编写代码。
class ArithmeticProgression(Progression):"""等差数列"""def __init__(self, start=0, increment=1, num=10):"""创建一个新的等差数列:param increment: 等差常量,默认为1:param start: 数列首项,默认为0:param num: 打印数列时的默认显示项数"""super().__init__(start=start, num=num)self._increment = incrementdef _advance(self):# 重写父类同名方法"""根据等差数列通项规则,生成任意项"""self._current += self._increment

等比数列

在等比数列的实现中,由于继承了Progression类,所以:

__init__方法中调用了父类初始化方法,从而对继承自父类的_current以及默认打印的数列项数_num进行了初始化,另外还对该类特有的等比数列常量_base进行了初始化;_advance方法按照等比数列通项规则对父类同名方法进行了重写;__next____iter____str__方法继承自Progression类,无需重复编写代码。
class GeometricProgression(Progression):"""等比数列"""def __init__(self, start=1, num=10, base=2):"""创建一个新的等比数列:param base: 等比常量,默认值为2:param start: 数列首项,默认为1:param num: 打印数列时的默认显示项数"""super().__init__(start=start, num=num)self._base = basedef _advance(self):"""根据等比数列通项规则,生成任意项"""self._current *= self._base

斐波那契数列

在斐波那契数列的实现中,由于继承了Progression类,所以:

__init__方法中调用了父类初始化方法,从而对继承自父类的_current以及默认打印的数列项数_num进行了初始化,另外还对该类特有的假想第0项_prev进行了初始化;_advance方法按照斐波那契数列通项规则对父类同名方法进行了重写;__next____iter____str__方法继承自Progression类,无需重复编写代码。
class FibonacciProgression(Progression):"""斐波那契数列"""def __init__(self, first=0, second=1, num=10):"""创建一个新的斐波那契数列:param first: 数列第一项,默认为0:param second: 数列第二项,默认为1:param num: 打印数列时的默认显示项数"""super().__init__(start=first, num=num)self._prev = second - first# 假想在第一项之前存在的第零项def _advance(self):"""根据斐波那契数列通项规则,生成任意项"""self._prev, self._current = self._current, self._prev + self._current

下面是对上述几个数列实现类的测试结果:

if __name__ == '__main__':print('默认数列Progression:')print(Progression(num=5), end='\n'*2)# [0, 1, 2, 3, 4]print('等差数列ArithmeticProgression:')print(ArithmeticProgression(start=10, increment=3, num=7), end='\n'*2)# [10, 13, 16, 19, 22, 25, 28]print('等比数列GeometricProgression:')print(GeometricProgression(start=4, base=3, num=9), end='\n'*2)# [4, 12, 36, 108, 324, 972, 2916, 8748, 26244]print('斐波那契数列FibonacciProgression:')print(FibonacciProgression(first=2, num=12))# [2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 123, 199]

二、抽象基类

仔细分析上述代码可知,基类Progression仅是为了作为ArithmeticProgressionGeometricProgression以及FibonacciProgression的基类,虽然可以通过实例化Progression得到一个对象,但这意义不大,因为其仅是ArithmeticProgression的一种特殊情况,即第一项为0,等差常量为1的等差数列。

在支持面向对象编程范式的语言中,对Progression这种仅作为基类用于指定多个子类所需实现的方法的类,有一个专门的术语——抽象基类。

在Python3中想要定义一个抽象基类,可通过如下步骤实现:

在定义抽象基类前,从模块abc中导入类ABCMeta和方法abstractmethod;在定义抽象基类时: 在抽象基类名后指定metaclassABCMeta;在抽象基类需被子类继承后实现的方法(一般称为抽象方法)前使用@abstractmethod

例如:如前所述,对于Progression方法,按照上述流程将其定义为抽象基类的代码如下:

from abc import ABCMeta, abstractmethodclass Progression(metaclass=ABCMeta):"""数列基类"""def __init__(self, start=0, num=10):"""将当前数列的第一项初始化为0:param start: 数列第一项,默认为0:param num: 打印数列时的默认显示项数"""self._current = startself._num = num@abstractmethoddef _advance(self):"""用于根据数列前若干项进行任意项的生成,该方法应该被子类重写"""def __next__(self):"""迭代器协议方法,返回数列中的下一项,当已至数列最后一项则抛出StopIteration异常"""if self._num > 0:ans = self._currentself._advance()self._num -= 1return anselse:raise StopIterationdef __iter__(self):"""迭代器协议方法,返回对象自身"""return selfdef __str__(self):"""返回对象的字符串表示形式"""return str(list(self))

可以看出,上述代码中我们将_advance定义成了抽象方法,因为该方法一方面在所有子类中都必须存在,另一方面该方法在所有子类中的实现又都完全不同。

需要指出的是,对于抽象基类(如:Progression)不能直接对其通过实例化创建对象,否则会报这样的错误:TypeError: Can't instantiate abstract class Progression with abstract methods _advance

三、接口

接口是一种编程机制,这种机制可以确保不同的代码编写者可以:

遵循相同的代码签名,如:方法名称(_advance)、参数、返回值;使用不同的算法实现具体代码,如:根据等差、等比、斐波那契数列的通项生成规则实现_advance方法。

接口机制的好处在于,可以:

实现不同代码编写者之间的协作,如:应用架构者可以对整体框架做搭建,而将具体实现留给实施人员,这有点像你的老板一般会告诉你要做根据手头的资源某几件事情以及期望结果是什么,而你和你的同事需要通过一定的过程努力将每一件事具体实施好;实现代码间的松耦合,各个接口的实现人员无需了解其他人员对接口的内部具体实现。

Python中,对于接口的具体实现,只要在子类中继承抽象基类,然后实现其中的所有抽象方法即可。

下面还是以上述的数列类为例演示接口实现的过程:

from abc import ABCMeta, abstractmethodclass Progression(metaclass=ABCMeta):"""数列基类"""def __init__(self, start=0, num=10):"""将当前数列的第一项初始化为0:param start: 数列第一项,默认为0:param num: 打印数列时的默认显示项数"""self._current = startself._num = num@abstractmethoddef _advance(self):"""用于根据数列前若干项进行任意项的生成,该方法应该被子类重写"""def __next__(self):"""迭代器协议方法,返回数列中的下一项,当已至数列最后一项则抛出StopIteration异常"""if self._num > 0:ans = self._currentself._advance()self._num -= 1return anselse:raise StopIterationdef __iter__(self):"""迭代器协议方法,返回对象自身"""return selfdef __str__(self):"""返回对象的字符串表示形式"""return str(list(self))class FibonacciProgression(Progression):"""斐波那契数列"""def __init__(self, first=0, num=10, second=1):"""创建一个新的斐波那契数列:param first: 数列第一项,默认为0:param second: 数列第二项,默认为1:param num: 打印数列时的默认显示项数"""super().__init__(start=first, num=num)self._prev = second - first# 假想在第一项之前存在的第零项def _advance(self):"""根据斐波那契数列通项规则,生成任意项"""self._prev, self._current = self._current, self._prev + self._currentif __name__ == '__main__':print(FibonacciProgression(first=2, num=12))# [2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 123, 199]

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。