Python多继承的坑与MRO C3广度优先算法
云栖号资讯:【点击查看更多行业资讯】
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!
前言
继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类的行为在编译期就已经决定,无法在运行期扩展。
有些编程语言支持多重继承,即一个子类可以同时有多个父类,比如C++编程语言;而在有些编程语言中,一个子类只能继承自一个父类,比如Java编程语言,这时可以透过实现接口来实现与多重继承相似的效果。
现今面向对象程序设计技巧中,继承并非以继承类别的“行为”为主,而是继承类别的“类型”,使得组件的类型一致。另外在设计模式中提到一个守则,“多用合成,少用继承”,此守则也是用来处理继承无法在运行期动态扩展行为的遗憾。
Python的继承
Python有单继承与多继承。单继承即子类继承于一个类,多继承即子类继承于多个类,多继承会比较少遇到,本章节主要讲单继承。
单继承
在继承中基类的构造方法(init()方法)不会被自动调用,它需要在其派生类的构造方法中亲自专门调用。
在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。而在类中调用普通函数时并不需要带上self参数
Python 总是首先查找对应类的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找) Python单继承的案例:
class Animal(object): def __init__(self, name, age): self.name = name self.age = age def call(self): print(self.name, '会叫') class Cat(Animal): def __init__(self,name,age,sex): super(Cat, self).__init__(name,age) # 不要忘记从Animal类执行初始化 self.sex=sex if __name__ == '__main__': c = Cat('喵喵', 2, '男') c.call() # 输出 喵喵 会叫 ,Cat继承了父类Animal的方法
多继承
对于多继承,一个子类可以用多个父类的方法,这里我们先给一个案例,再来详解如果多个父类存在同名方法、父类中又有父类的同名方法时的执行顺序 demo:
class Plant: def __init__(self, color): print("init Plant start") self.color = color print("init Plant end") def show(self): print("The Plant color is:", self.color) class Fruit: def __init__(self, color): print("init Fruit start") self.color = color print("init Fruit end") def show(self): print("The Fruit color is:", self.color) class Melon(Fruit): def __init__(self, color): print("init Melon start") super(Melon, self).__init__(color) self.color = color print("init Melon end") def show(self): print("The Melon color is:", self.color) class Mango(Fruit, Plant): def __init__(self, color): print("init Mango start") Plant.__init__(self, color) Fruit.__init__(self, color) self.color = color print("init Mango end") def show_color(self): print("The Mango color is:", self.color) class Watermelon(Melon, Plant): def __init__(self, color): print("init Watermelon start") Melon.__init__(self, color) Plant.__init__(self, color) self.color = color print("init Watermelon end") def show_color(self): print("The Watermelon color is:", self.color) if __name__ == "__main__": mango = Mango("yellow") mango.show() watermelon = Watermelon("red") watermelon.show()
我们定义了植物Plant类和水果Fruit类,然后定义瓜Melon类继承水果类,定义一个芒果Mango类继承Fruit和Plant类,定义一个西瓜Watermelon类继承Melon类和Plant类。 执行结果为:
init Mango start init Plant start init Plant end init Fruit start init Fruit end init Mango end The Fruit color is: yellow init Watermelon start init Melon start init Fruit start init Fruit end init Melon end init Plant start init Plant end init Watermelon end The Melon color is: red
多继承的方法执行顺序
从上面的案例我们可以看出:
Mango类即使初始化顺序先初始化了Plant,再初始化Fruit类,但是执行的同名方法的顺序仍然按定义该类是括号中的继承顺序;
Watermelon类也是按括号中的继承顺序来执行同名方法,但是执行的Melon类同名方法中,即使有该执行的Melon类的父类Fruit类也有同名方法,但还是优先执行该Melon类,这与一般的单继承规则一致。 总结如下:
- 定义派生类时,需要注意圆括号中继承父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法;
- 如果继承的父类中,该父类还继承了别的父类,则调用同名方法时是调用最先访问到的同名方法;
- 支持多层父类继承,子类会继承父类所有的属性和方法,包括父类的父类的所有属性 和 方法。
多继承时通过super方法初始化
单继承中我们往往会使用super方法来初始化父类init方法,因为super方法可以防止同一个类被实例化多次。 但是这在多继承中往往会出现问题,我们把上面的例子改为如下demo:
class Plant(object): def __init__(self, color): print("init Plant start") self.color = color print("init Plant end") def show(self): print("The Plant color is:", self.color) class Fruit(object): def __init__(self, color): print("init Fruit start") self.color = color print("init Fruit end") def show(self): print("The Fruit color is:", self.color) class Melon(Fruit): def __init__(self, color): print("init Melon start") super(Melon, self).__init__(color) self.color = color print("init Melon end") def show(self): print("The Melon color is:", self.color) class Mango(Fruit, Plant): def __init__(self, color): print("init Mango start") super(Mango, self).__init__(color) self.color = color print("init Mango end") def show_color(self): print("The Mango color is:", self.color) class Watermelon(Melon, Plant): def __init__(self, color): print("init Watermelon start") super(Watermelon, self).__init__(color) self.color = color print("init Watermelon end") def show_color(self): print("The Watermelon color is:", self.color) if __name__ == "__main__": mango = Mango("yellow") mango.show() watermelon = Watermelon("red") watermelon.show()
可以看到,我们只是把上面Mango类和Watermelon类初始化父类的方法改为了super方式,但是执行结果却如下:
init Mango start init Fruit start init Fruit end init Mango end The Fruit color is: yellow init Watermelon start init Melon start init Fruit start init Fruit end init Melon end init Watermelon end The Melon color is: red
可以看到,两个实例中继承顺序排在后面的Plant类都没有被实例化,所以多继承中采用super是会有问题的,它只会实例化一次,所以super类并不适合使用在多继承场合。 其实导致这个问题的原因是Python中的一个MRO(Method Resolution Order)继承调用顺序的算法,我们要修复上面的问题,可以在Fruit类中加入super方法,把Fruit类改为:
class Fruit(object): def __init__(self, color): super().__init__(color) print("init Fruit start") self.color = color print("init Fruit end") def show(self): print("The Fruit color is:", self.color)
就可以达到实例化两个类的作用了
关于Python的mro方法
Python 中针对 类 提供了一个内置属性 mro 可以查看方法搜索顺序 MRO 是 method resolution order,主要用于在多继承时判断 方法、属性 的调用 路径 Python2.3以上,MRO算法就已经改为了广度优先,防止最顶级的类被多次实例化而耗费资源:
从C.mro的值可以看出, Python的方法解析优先级从高到低为:
- 实例本身(instance)
- 类(class)
- super class, 继承关系越近, 越先定义, 优先级越高. 例如我们执行print(Mango.__mro__)会得到如下返回: (, , , )
Python多继承的注意事项
在单继承场景:super().init等同于类名.init;
在多继承场景,super方法只能保证第一个父类被初始化,除非每一个继承的父类都实现super初始化,最终初始化了根类Object类;
另外,子类从多个父类派生,而子类又没有自己的构造函数时:
1. 按顺序继承,哪个父类在最前面且它又有自己的构造函数,就继承它的构造函数; 2. 如果最前面第一个父类没有构造函数,则继承第2个的构造函数,第2个没有的话,再往后找,以此类推。
【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK
原文发布时间:2020-08-03
本文作者:henry_czh
本文来自:“掘金”,了解相关信息可以关注“掘金”
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java 四种引用类型,强引用、软引用、弱引用、虚引用
云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 前言 每种编程语言都有自己操作内存中元素的方式,例如在 C 和 C++ 里是通过指针,对于指针的使用,大家肯定记得刚学习时候的痛苦。 作为 OOP 面向对象编程的翘楚,在 Java 中一切都被视为了对象。但其实操作时候的标识符并不是真正的对象,而是对象的一个引用(reference)。 通过将这个叫“引用”的标识符指向某个对象,之后便可以使用这个引用来实现操作对象了。 在 JDK1.2 之前,Java中的定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称为这块内存代表着一个引用。 Java 中的垃圾回收机制在判断是否回收某个对象的时候,都需要依据“引用”这个概念。不同垃圾回收算法中,对引用的判断方式有所不同,典型的有引用计数法和可达性分析法。 JDK1.2 之前,一个对象只有“已被引用”和"未被引用"两种状态,这将无法描述某些特殊情况下的对象,比如,当内存充足时需要保留,而内存紧张时才需要被抛弃的一类对象。 所以在 JDK.1.2 之后,Ja...
- 下一篇
MetInfo V7.1.0 细节优化,内含补丁包 20200803
MetInfo7.1.0 2020年8月3日系统升级更新: 修复系统文件上传漏洞; 修复系统xss漏洞; 修复备份数据导入可能意外终止的问题; 修复部分模板演示视频不显示的问题; 优化系统代码。 修复方法: 方法一、商业版用户可以直接在网站后台使用“系统诊所”插件在线修复; 方法二、下载本次优化文件包,覆盖网站对应文件:update20200803.zip 你需要先将网站升级到7.1.0正式版后再使用此文件覆盖。 方法三、直接使用码云或Git的开源包,同步更新文件: https://gitee.com/MetInfo_1/MetInfo https://github.com/metinfo/metinfo MetInfo官网的安装包和升级包均已更新的到2020年8月3日的文件状态,在此时间之后安装和升级的用户无需执行以上操作。 免费模板下载站: https://www.metcms.cn/(已上线118套免费模板,遵守最终用户许可协议即可免费商用) 米拓企业建站系统主要用于搭建企业网站,采用PHP+MySQL架构,支持SQLite数据库,全站内置了SEO搜索引擎优化机制,支持用户...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Linux系统CentOS6、CentOS7手动修改IP地址
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Red5直播服务器,属于Java语言的直播服务器
- CentOS6,CentOS7官方镜像安装Oracle11G
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池