注册 登录
  • 欢迎访问"运维那点事",推荐使用Google浏览器访问,可以扫码关注本站的"微信公众号"。
  • 如果您觉得本站对你有帮助,那么可以扫码捐助以帮助本站更好地发展。

Python面向对象:类继承之super函数

Python编程 彭东稳 3520次浏览 已收录 0个评论

一、类多重继承

Python是支持类中多重继承的,概念虽然容易,但是困难的工作是如果子类调用一个自身没有定义的属性,它是按照何种顺序去到父类寻找呢,尤其是众多父类中有多个都包含该同名属性。

我们知道Python的类分为经典类与新式类。Python2.7之前的版本中可以采用经典类,经典类继承父类的顺序采用深度优先算法,但在Python3之后的版本就只承认新式类了。新式类在python2.2之后的版本中都可以使用,新式类的继承顺序采用C3算法,其继承顺序可以通过查看MRO列表获取。经典类没有__MRO__instance.mro()调用,而新式类有。

经典类中采用深度优先的匹配方法,可能导致在查询继承树中绕过后面的父类(在Python 2中测试,Python默认使用新式类):

结果如下:

新式类采用C3算法(区别于广度优先的原则)进行搜索,若使用新式类:

结果如下:

这里为什么类D没有打印出来呢?因为类C跟类D有相同的方法,子类把父类给覆盖了。如果也想打印父类可以使用super方法(下面会介绍super),如下:

此时再去初始化类A,结果如下:

经典类和新式类各自搜索的顺序如下图所示:

Python面向对象:类继承之super函数

C3算法最早被提出是用于Lisp的,应用在Python中是为了解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题。

  • 本地优先级:指声明时父类的顺序,比如A(B,C),如果访问A类对象属性时,应该根据声明顺序,优先查找B类,然后再查找C类。
  • 单调性:如果在A的解析顺序中,B排在C的前面,那么在A的所有子类里,也必须满足这个顺序。

对于下面这一段程序:

当初始化实例F = F()时,使用深度优先搜索,广度优先搜索及C3算法的不同搜索顺序如下:

Python面向对象:类继承之super函数

对于新式类,可以用instance.__mro__instance.mro()来查看其MRO(Method Resolution Order 方法解析顺序)列表。对于上文代码中的类F的MRO如下:

结果即C3算法的解析结果。C3线性化算法我们就不去深究了(太深入),感兴趣的读者可以自己去了解一下,总的来说,一个类的MRO列表就是合并所有父类的 MRO 列表,并遵循以下三条原则:

  • 子类永远在父类前面。
  • 如果有多个父类,会根据它们在列表中的顺序被检查。
  • 如果对下一个类存在两个合法的选择,选择第一个父类。

同时为了解决多重继承中子类和父类有重复方法名的问题,Python 2.2之后引入了super函数。

二、super()使用

在类的继承中,如果重定义某个方法时,该方法会覆盖父类的同名方法。如下:

初始化类时就会报错x变量找不到:

当然这个问题你可以通过直接引用父类方法名( Base.__init__(self)),或者子类不使用相同的方法名来解决,但是都不够友好,并且绝对引用修改起来也是灾难性的。

因此,自Python 2.2开始,Python添加了一个关键字super,来解决这个问题。下面是Python 2.3的官方文档说明:

从说明来看super只能工作在新式类下,其继承object。从示例来看,我们可以把Sub子类改一下,如下:

super()函数的一个常见用法是在 __init__()方法中确保父类被正确的初始化。

这回初始化Sub类就没有问题了,如下:

可以看到使用super后把代码的维护量降到最低,是一个不错的用法。另外在Python 3里语法有所改变:你可以用 super().__init__()替换 super(Sub, self).__init__(),更简洁,很Nice。

对于 super(Sub, self).__init__()是这样理解的:super(Sub, self)首先找到Sub的父类(就是类Base),然后把类Sub的对象self转换为类Base的对象(通过某种方式,一直没有考究是什么方式,惭愧),然后“被转换”的类Base对象调用自己的 __init__函数。

看了上面的使用,你可能会觉得super的使用很简单,无非就是获取了父类,并调用父类的方法。其实,在上面的情况下,super获得的类刚好是父类,但在其他情况就不一定了,super其实和父类没有实质性的关联。

如下,让我们看一个稍微复杂的例子,涉及到多重继承。代码如下:

其中,D是父类,B、C继承自D,A继承自B、C;它们的继承关系如下:

现在,让我们看一下结果:

按照我们理想的结果显示出来了(使用新式类)。

我们看一下A的MRO(Method Resolution Order)方法的解析顺序,如下:

从这个例子可以看出,super和父类没有实质性的关联。下面让我们搞清super是怎么运作的。

三、super原理

super的工作原理如下:

其中,cls 代表类,inst 代表实例,上面的代码做了两件事:

  • 获取 inst 的 MRO 列表
  • 查找 cls 在当前 MRO 列表中的 index, 并返回它的下一个类,即 mro[index + 1]

当你使用 super(cls, inst) 时,Python 会在 inst 的 MRO 列表上搜索 cls 的下一个类。

现在,让我们回到前面的例子。

首先看类A的 __init__方法:

这里的self是当前A的实例,self.__class__.mro()结果是:

可以看到,A的下一个类是B,于是,跳到了B的 __init__,这时会打印出B,并执行下面一行代码:

注意,这里的self也是当前A的实例,MRO列表跟上面是一样的,搜索B在MRO中的下一个类,发现是C,于是,跳到了C的 __init__,这时会打印出class C。

整个过程还是比较清晰的,关键是要理解super的工作方式,而不是想当然地认为super调用了父类的方法。通过上面的类继承讲解,知道主要是新式类的类搜索顺序发生了改变;所以super是依赖新式类,不然无法工作。

<参考>

Python:super方法

调用父类方法


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (1)or分享 (0)
关于作者:

您必须 登录 才能发表评论!