Python中super的用法

作者:漩涡鸣人

网址:

https://www.cnblogs.com/lovemo1314/archive/2011/05/03/2035005.html

有部分改动,新增和删除了部分内容。

正文

我们在进行面向对象编程的过程中,经常存在覆盖掉父类的相应同名方法或者通过调用父类方法的情况,具体怎么使用,估计很多人搞不清楚。我们还是通过具体的例子来理解吧!

1

问题的发现与提出

在Python类的方法(method)中,要调用父类的某个方法,在Python 2.2以前,通常的写法如代码段1

即,使用非绑定的类方法(用类名来引用的方法),并在参数列表中,引入待绑定的对象(self),从而达到调用父类的目的。

这样做的缺点是,当一个子类的父类发生变化时(如类B的父类由A变为C时),必须遍历整个类定义,把所有的通过非绑定的方法的类名全部替换过来,例如代码段2

如果代码简单,这样的改动或许还可以接受。但如果代码量庞大,这样的修改可能是灾难性的。

因此,自Python 2.2开始,Python添加了一个关键字super,来解决这个问题。下面是Python3.5的官方文档说明:https://docs.python.org/3.5/library/functions.html?highlight=super#super

二维码直达

调用父类或兄弟类的方法返回给代理对象。这对访问在类中被覆盖的父类继承方法很有用。

super有两种典型的用例。在具有单一继承的类层次结构中,super可以用于引用父类,而不用明确地命名它们,从而使代码更易于维护。这种用法与其他编程语言中的调用父类用法非常类似。

典型的父类调用如下所示:

从说明来看,可以把类B改写如代码段3

尝试执行上面同样的代码,结果一致,但修改的代码只有一处,把代码的维护量降到最低,是一个不错的用法。因此在我们的开发过程中,super关键字被大量使用,而且一直表现良好。

在我们的印象中,对于super(B, self).init()是这样理解的:super(B,self)首先找到B的父类(就是类A),然后把类B的对象self转换为类A的对象(通过某种方式,一直没有考究是什么方式,惭愧),然后“被转换”的类A对象调用自己的init函数。考虑到super中只有指明子类的机制,因此,在多继承的类定义中,通常我们保留使用类似代码段1的方法。

2

较为复杂的情况

有一天某同事设计了一个相对复杂的类体系结构(我们先不要管这个类体系设计得是否合理,仅把这个例子作为一个题目来研究就好),代码如代码段4

明显地,类A和类D的初始化函数被重复调用了2次,这并不是我们所期望的结果!我们所期望的结果是最多只有类A的初始化函数被调用2次——其实这是多继承的类体系必须面对的问题。我们把代码段4的类体系画出来,如下图:

按我们对super的理解,从图中可以看出,在调用类C的初始化函数时,应该是调用类A的初始化函数,但事实上却调用了类D的初始化函数。好一个诡异的问题!为什么?

3

MRO?

为什么多重继承要按照这样的顺序继承呢?因为对于多继承, 就要遵循MRO表中的顺序了。现在又有一个新的问题了,MRO记录,这个什么玩意?

MRO就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表。查看MRO的记录,发觉代码4包含7个元素,7个类名分别为:F E B C D A object

4

再来一段

现在我们再来修改一段代码看看。

明显地,F的初始化不仅完成了所有的父类的调用,而且保证了每一个父类的初始化函数只调用一次。

再看类结构:

E-1,D-2是F的父类,其中表示E类在前,即F(E,D)。

所以初始化顺序可以从类结构图来看出 : F->E->B C D A

由于C,D有同一个父类,因此会先初始化D再是A。

5

延续的讨论

我们再重新看上面的类体系图,如果把每一个类看作图的一个节点,每一个从子类到父类的直接继承关系看作一条有向边,那么该体系图将变为一个有向图。不难发现MRO的顺序正好是该有向图的一个拓扑排序序列。

从而,我们得到了另一个结果——Python是如何去处理多继承。支持多继承的传统的面向对象程序语言(如C++)是通过虚拟继承的方式去实现多继承中父类的构造函数被多次调用的问题,而Python则通过MRO的方式去处理。但这给我们一个难题:对于提供类体系的编写者来说,他不知道使用者会怎么使用他的类体系,也就是说,不正确的后续类,可能会导致原有类体系的错误,而且这样的错误非常隐蔽的,也难于发现。

正如《松本行宏的程序世界》2.3中讲解了多重继承的缺点:

结构复杂化,类之间的关系复杂

优先顺序模糊,具有复杂的父类的类,它们的优先关系一下子很难辨认清楚

功能冲突,当不同父类中有相同的方法时就会产生冲突

使用要慎重啊~!(这个出资小杨语录:))

6

小结

super并不是一个函数,是一个类名,形如super(B, self)事实上调用了super类的初始化函数,产生了一个super对象;

super类的初始化函数并没有做什么特殊的操作,只是简单记录了类类型和具体实例;

super(B, self).func的调用并不是用于调用当前类的父类的func函数;

Python的多继承类是通过MRO的方式来保证各个父类的函数被逐一调用,而且保证每个父类函数只调用一次(如果每个类都使用super);

混用super类和非绑定的函数是一个危险行为,这可能导致应该调用的父类函数没有调用或者一个父类函数被调用多次。

更多资讯请关注:学点编程吧——你私人的计算机学习顾问!

欢迎各位童鞋留言!

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20171211G0ZWYR00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券