引言:

  在类的多继承使用场景中,重写父类的方法时,可能会考虑到需要重新调用父类的方法,所以super()函数就是比较使用也很必要的解决方法;

文章来源:

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

一、问题的发现与提出

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

代码段1:

class A:
  def __init__(self):
   print "enter A"
   print "leave A"

 class B(A):
  def __init__(self):
   print "enter B"
   A.__init__(self)
   print "leave B"

 >>> b = B()
 enter B
 enter A
 leave A
 leave B

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

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

代码段2:

class B(C):    # A --> C
  def __init__(self):
   print "enter B"
   C.__init__(self) # A --> C
   print "leave B"

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

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

super(type[, object-or-type])

Return the superclass of type. If the second argument is omitted the super object
  returned is unbound. If the second argument is an object, isinstance(obj, type) 
  must be true. If the second argument is a type, issubclass(type2, type) must be 
  true. super() only works for new-style classes.

A typical use for calling a cooperative superclass method is:

class C(B):
       def meth(self, arg):
           super(C, self).meth(arg)

New in version 2.2.

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

代码段3:

class A(object):    # A must be new-style class
  def __init__(self):
   print "enter A"
   print "leave A"

class B(C):     # A --> C
  def __init__(self):
   print "enter B"
   super(B, self).__init__()
   print "leave B"

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

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

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

代码段4:

class A(object):
  def __init__(self):
   print "enter A"
   print "leave A"

 class B(object):
  def __init__(self):
   print "enter B"
   print "leave B"

 class C(A):
  def __init__(self):
   print "enter C"
   super(C, self).__init__()
   print "leave C"

 class D(A):
  def __init__(self):
   print "enter D"
   super(D, self).__init__()
   print "leave D"
 class E(B, C):
  def __init__(self):
   print "enter E"
   B.__init__(self)
   C.__init__(self)
   print "leave E"

 class F(E, D):
  def __init__(self):
   print "enter F"
   E.__init__(self)
   D.__init__(self)
   print "leave F"

f = F() result:

enter F
 enter E
 enter B
 leave B
 enter C
 enter D
 enter A
 leave A
 leave D
 leave C
 leave E
 enter D
 enter A
 leave A
 leave D
 leave F

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

object
   |       \
   |        A
   |      / |
   B  C  D
    \   /   |
      E    |
        \   |
          F

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

  也就是说,mro中记录了一个类的所有基类的类类型序列。查看mro的记录,发觉包含7个元素,7个类名分别为:

F E B C D A object

  从而说明了为什么在C.__init__中使用super(C, self).__init__()会调用类D的初始化函数了。 ???

  我们把代码段4改写为:

代码段9:

class A(object):
  def __init__(self):
   print "enter A"
   super(A, self).__init__()  # new
   print "leave A"

 class B(object):
  def __init__(self):
   print "enter B"
   super(B, self).__init__()  # new
   print "leave B"

 class C(A):
  def __init__(self):
   print "enter C"
   super(C, self).__init__()
   print "leave C"

 class D(A):
  def __init__(self):
   print "enter D"
   super(D, self).__init__()
   print "leave D"
 class E(B, C):
  def __init__(self):
   print "enter E"
   super(E, self).__init__()  # change
   print "leave E"

 class F(E, D):
  def __init__(self):
   print "enter F"
   super(F, self).__init__()  # change
   print "leave F"

f = F() result:

enter F
 enter E
 enter B
 enter C
 enter D
 enter A
 leave A
 leave D
 leave C
 leave B
 leave E
 leave F

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

  再看类结构:

    object
     /   \
    /      A
   |     /   \
  B-1  C-2   D-2
    \   /    /
     E-1    /
        \  /
          F

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

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

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

三、延续的讨论

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

  从而,我们得到了另一个结果——Python是如何去处理多继承。支持多继承的传统的面向对象程序语言(如C++)是通过虚拟继承的方式去实现多继承中父类的构造函数被多次调用的问题,而Python则通过mro的方式去处理。

  但这给我们一个难题:对于提供类体系的编写者来说,他不知道使用者会怎么使用他的类体系,也就是说,不正确的后续类,可能会导致原有类体系的错误,而且这样的错误非常隐蔽的,也难于发现。

四、小结

  1. super并不是一个函数,是一个类名,形如super(B, self)事实上调用了super类的初始化函数,
       产生了一个super对象;
  2. super类的初始化函数并没有做什么特殊的操作,只是简单记录了类类型和具体实例;
  3. super(B, self).func的调用并不是用于调用当前类的父类的func函数;
  4. Python的多继承类是通过mro的方式来保证各个父类的函数被逐一调用,而且保证每个父类函数
       只调用一次(如果每个类都使用super);
  5. 混用super类和非绑定的函数是一个危险行为,这可能导致应该调用的父类函数没有调用或者一
       个父类函数被调用多次。

python super()函数详解的更多相关文章

  1. Python3中的super()函数详解

    关于Python3中的super()函数 我们都知道,在Python3中子类在继承父类的时候,当子类中的方法与父类中的方法重名时,子类中的方法会覆盖父类中的方法, 那么,如果我们想实现同时调用父类和子 ...

  2. Python super继承详解

    MRO(Method resolution order)是python用来解析方法调用顺序的,mro中记录了一个类的所有基类的类类型序列,super不是简单地调用基类的方法,而是按照MRO中的顺序来调 ...

  3. Python匿名函数详解

    python 使用 lambda 来创建匿名函数. lambda这个名称来自于LISP,而LISP则是从lambda calculus(一种符号逻辑形式)取这个名称的. 在Python中,lambda ...

  4. python super()使用详解

    1.super的作用调用父类方法2.单继承使用示例 #coding:utf-8 #单继承 class A(object): def __init__(self): self.n=2 def add(s ...

  5. 04 python之函数详解

    一.函数初识 函数的产生:函数就是封装一个功能的代码片段. li = ['spring', 'summer', 'autumn', 'winter'] def function(): count = ...

  6. Python 钩子函数详解

    ###### 钩子函数 ``` import pluggy hookspec = pluggy.HookspecMarker('aaa') hookimpl = pluggy.HookimplMark ...

  7. Python sorted函数详解(高级篇)

    sorted() 函数对所有可迭代的对象进行排序操作. sort 与 sorted 区别: sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作. list 的 s ...

  8. python socket函数详解

    关于socket函数,每个的意义和基本功能都知道,但每次使用都会去百度,参数到底是什么,返回值代表什么意义,就是说用的少,也记得不够精确.每次都查半天,经常烦恼于此.索性都弄得清楚.通透,并记录下来, ...

  9. Python print函数详解

    1 """ 2 print(...) 3 print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=Fals ...

随机推荐

  1. WinRAR的自解压模式 - imsoft.cnblogs

    一个 SFX (SelF-eXtracting)自解压文件是压缩文件的一种,它结合了可执行文件模块,一种用以运行从压缩文件解压文件的模块.这样的压缩文件不需要外部程序来解压自解压文件的内容,它自己便可 ...

  2. WinRAR备份技巧 - imsoft.cnblogs

    RAR控制台日常备份策略 run.batrar a -ep1 -agYYYY{年}MM{月}DD{日} 备份 @list.txt-ep1是忽略原文件路径,rar包里是一堆文件,没有目录结构-ag附加命 ...

  3. Spring DI

    一.   Spring DI 依赖注入 利用spring IOC实例化了对象,而DI将实例化的对象注入到需要对象的地方,完成初始化任务. 对象由spring创建,之后再由spring给属性赋值 spr ...

  4. with() {} 的用法

    var use = "other"; var katana = { isSharp: true, use: function(){ this.isSharp = !!this.is ...

  5. jQuery--- .hasOwnProperty 用法

    ☆ obj.hasOwnProperty('prop'): 是用来判断一个对象是否有你给出名称的属性或对象.不过需要注意的是, 此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个 ...

  6. POJ2311 Cutting Game

    题意 Language:Default Cutting Game Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 6007 Acc ...

  7. post请求(headers里有属性)报错:Request header field xxx is not allowed by Access-Control-Allow-Headers in preflight response

    post 请求,headers里有属性(xxx).请求时报错: XMLHttpRequest cannot load <url>. Request header field xxx is ...

  8. jquery操作select大全详解

    每一次操作select的时候,总是要出来翻一下资料,不如自己总结一下,以后就翻这里了. 比如<select class="selector"></select&g ...

  9. Ubuntu 安装lrzsz工具

    rz(上传) sz(下载)) 1. 安装 sudo apt-get install lrzsz 2. rz可覆盖原文件 rz -y

  10. QCheckBox控件

    一个QCheckBox会有2种状态:选中和为选中.它由一个选择框和一个label组成,常常用来表示应用的某些特性是启用或不启用. 在下面的例子中,我们创建了一个选择框,它的状态变化会引起窗口标题的变化 ...