当前位置: 首页 > news >正文

(转载)深入super,看Python如何解决钻石继承难题

1.   Python的继承以及调用父类成员

python子类调用父类成员有2种方法,分别是普通方法和super方法

假设Base是基类

class Base(object):
      def __init__(self):
           print “Base init”

则普通方法如下

class Leaf(Base):
       def __init__(self):
              Base.__init__(self)
              print “Leaf init”

super方法如下

class Leaf(Base):
       def __init__(self):
              super(Leaf, self).__init__()
              print “Leaf init”

上面的简单场景下,两种方法的效果一致:
>>> leaf = Leaf()

Base init

Leaf init

 

2.   钻石继承遇到的难题

当我们来到钻石继承场景时,我们就遇到了一个难题:

如果我们还是使用普通方法调用父类成员,代码如下:

class Base(object):
    def __init__(self):
        print “Base init”
class Medium1(Base):
    def __init__(self):
        Base.__init__(self)
        print “Medium1 init”
class Medium2(Base):
    def __init__(self):
        Base.__init__(self)
        print “Medium2 init”
class Leaf(Medium1, Medium2):
    def __init__(self):
        Medium1.__init__(self)
        Medium2.__init__(self)
        print “Leaf init”

 

当我们生成Leaf对象时,结果如下:

>>> leaf = Leaf()

Base init

Medium1 init

Base init

Medium2 init

Leaf init

可以看到Base被初始化了 两次 !这是由于Medium1和Medium2各自调用了Base的初始化函数导致的。

3.   各语言的解决方法

钻石继承中,父类被多次初始化是个非常难缠的问题,我们来看看其他各个语言是如何解决这个问题的:

3.1. C++

C++使用虚拟继承来解决钻石继承问题。

Medium1和Medium2虚拟继承Base。当生成Leaf对象时,Medium1和Medium2并不会自动调用虚拟基类Base的构造函数,而需要由Leaf的构造函数显式调用Base的构造函数。

3.2. Java

Java禁止使用多继承。

Java使用单继承+接口实现的方式来替代多继承,避免了钻石继承产生的各种问题。

3.3. Ruby

Ruby禁止使用多继承。

Ruby和Java一样只支持单继承,但它对多继承的替代方式和Java不同。Ruby使用Mixin的方式来替代,在当前类中mixin入其他模块,来做到代码的组装效果。

3.4. Python

Python和C++一样,支持多继承的语法。但Python的解决思路和C++完全不一样,Python是的用就是super

我们把第2章的钻石继承用super重写一下,看一下输出结果

class Base(object):
    def __init__(self):
        print “Base init”
class Medium1(Base):
    def __init__(self):
        super(Medium1, self).__init__()
        print “Medium1 init”
class Medium2(Base):
    def __init__(self):
        super(Medium2, self).__init__()
        print “Medium2 init”
class Leaf(Medium1, Medium2):
    def __init__(self):
        super(Leaf, self).__init__()
        print “Leaf init”

 

我们生成Leaf对象:

>>> leaf = Leaf()

Base init

Medium2 init

Medium1 init

Leaf init

可以看到整个初始化过程符合我们的预期,Base只被初始化了1次。而且重要的是,相比原来的普通写法,super方法并没有写额外的代码,也没有引入额外的概念

4.   super的内核:mro

要理解super的原理,就要先了解mro。mro是method resolution order的缩写,表示了类继承体系中的成员解析顺序。

在python中,每个类都有一个mro的类方法。我们来看一下钻石继承中,Leaf类的mro是什么样子的:

>>> Leaf.mro()

[<class '__main__.Leaf'>, <class '__main__.Medium1'>, <class '__main__.Medium2'>, <class '__main__.Base'>, <type 'object'>]

可以看到mro方法返回的是一个祖先类的列表。Leaf的每个祖先都在其中出现一次,这也是super在父类中查找成员的顺序。

通过mro,python巧妙地将多继承的图结构,转变为list的顺序结构。super在继承体系中向上的查找过程,变成了在mro中向右的线性查找过程,任何类都只会被处理一次。

通过这个方法,python解决了多继承中的2大难题:

1. 查找顺序问题。从Leaf的mro顺序可以看出,如果Leaf类通过super来访问父类成员,那么Medium1的成员会在Medium2之前被首先访问到。如果Medium1和Medium2都没有找到,最后再到Base中查找。

2. 钻石继承的多次初始化问题。在mro的list中,Base类只出现了一次。事实上任何类都只会在mro list中出现一次。这就确保了super向上调用的过程中,任何祖先类的方法都只会被执行一次。

至于mro的生成算法,可以参考这篇wiki:https://en.wikipedia.org/wiki/C3_linearization

5.   super的具体用法

我们首先来看一下python中的super文档

>>> help(super)

Help on class super in module __builtin__:

class super(object)

|  super(type, obj) -> bound super object; requires isinstance(obj, type)

|  super(type) -> unbound super object

|  super(type, type2) -> bound super object; requires issubclass(type2, type)

光从字面来看,这可以算是python中最语焉不详的帮助文档之一了。甚至里面还有一些术语误用。那super究竟应该怎么用呢,我们重点来看super中的第1和第3种用法

5.1. super(type, obj)

当我们在Leaf的__init__中写这样的super时:

class Leaf(Medium1, Medium2):
       def __init__(self):
              super(Leaf, self).__init__()
              print “Leaf init”

super(Leaf, self).__init__()的意思是说:

  1. 获取self所属类的mro, 也就是[Leaf, Medium1, Medium2, Base]
  2. 从mro中Leaf右边的一个类开始,依次寻找__init__函数。这里是从Medium1开始寻找
  3. 一旦找到,就把找到的__init__函数绑定到self对象,并返回

从这个执行流程可以看到,如果我们不想调用Medium1的__init__,而想要调用Medium2的__init__,那么super应该写成:super(Medium1, self)__init__()

5.2. super(type, type2)

当我们在Leaf中写类方法的super时:

class Leaf(Medium1, Medium2):
       def __new__(cls):
              obj = super(Leaf, cls).__new__(cls)
              print “Leaf new”
              return obj

super(Leaf, cls).__new__(cls)的意思是说:

  1. 获取cls这个类的mro,这里也是[Leaf, Medium1, Medium2, Base]
  2. 从mro中Leaf右边的一个类开始,依次寻找__new__函数
  3. 一旦找到,就返回“ 非绑定 ”的__new__函数

由于返回的是非绑定的函数对象,因此调用时不能省略函数的第一个参数。这也是这里调用__new__时,需要传入参数cls的原因

同样的,如果我们想从某个mro的某个位置开始查找,只需要修改super的第一个参数就行

6.   小结

至此,我们讲解了和super相关的用法及原理,小结一下我们讲过的内容有:

  1. python调用父类成员共有2种方法:普通方法,super方法
  2. 在钻石继承中,普通方法会遇到Base类两次初始化的问题
  3. 简述了其他语言对这个问题的解决方法,并用实例展示了python使用super可以解决此问题
  4. 在讲super具体用法前,先讲了super的内核:mro的知识和原理
  5. 讲解了super两种主要的用法及原理
 

转载于:https://www.cnblogs.com/czaiz/p/7772194.html

相关文章:

  • 1000. A+B Problem
  • 儿童上网时间管控软件_GreenSurfOnline V0.1 使用说明 (以Windows后台服务形式存在,安装需要有一定电脑操作基础)...
  • 没事儿别优化!
  • Java并发案例04---生产者消费者问题03--使用ReentrantLock
  • Java日志框架-logback的介绍及配置使用方法(纯Java工程)(转)
  • 行外人浅谈“云计算”
  • PE 文件格式 详解 二
  • Linux Mint 教程
  • Jenkins-权限控制
  • 阿里 Linux服务器外网无法连接MySQL解决方法
  • C语言博客作业--嵌套循环
  • java获取当前上一周、上一月、上一年的时间
  • mysql-proxy实现读写分离
  • Linux基本命令—权限管理、文件搜索、帮助、压缩解压、网络通信
  • 中英文金额大写转换器
  • (十五)java多线程之并发集合ArrayBlockingQueue
  • Django 博客开发教程 16 - 统计文章阅读量
  • emacs初体验
  • node.js
  • php中curl和soap方式请求服务超时问题
  • Python十分钟制作属于你自己的个性logo
  • Redis在Web项目中的应用与实践
  • SpringBoot 实战 (三) | 配置文件详解
  • spring学习第二天
  • SQLServer之索引简介
  • Vue.js-Day01
  • 关于List、List?、ListObject的区别
  • 欢迎参加第二届中国游戏开发者大会
  • 提醒我喝水chrome插件开发指南
  • NLPIR智能语义技术让大数据挖掘更简单
  • python最赚钱的4个方向,你最心动的是哪个?
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • ​DB-Engines 11月数据库排名:PostgreSQL坐稳同期涨幅榜冠军宝座
  • ​总结MySQL 的一些知识点:MySQL 选择数据库​
  • # 数论-逆元
  • #{}和${}的区别?
  • #include
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • (1)(1.13) SiK无线电高级配置(五)
  • (23)Linux的软硬连接
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (day 2)JavaScript学习笔记(基础之变量、常量和注释)
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (ZT)出版业改革:该死的死,该生的生
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (附源码)ssm码农论坛 毕业设计 231126
  • (原創) 是否该学PetShop将Model和BLL分开? (.NET) (N-Tier) (PetShop) (OO)
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • (轉貼) 寄發紅帖基本原則(教育部禮儀司頒布) (雜項)
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)
  • .Net环境下的缓存技术介绍
  • @AutoConfigurationPackage的使用
  • @RequestBody详解:用于获取请求体中的Json格式参数
  • [ 云计算 | AWS 实践 ] Java 如何重命名 Amazon S3 中的文件和文件夹