• python中的类是可以多继承的
  • python中的类方法和对象方法通过super调用父类的方法

上面两句话是对python class的一般认识,但其实第二句话是错的。

python中的继承

python与C++一样允许多继承,但是再使用一段时间后,我发现python中的继承与java、C++的继承似乎不太一样,不能直接用其它语言的继承概念来理解python的继承。

下面是一个python中多继承的例子:

class A(object):
 	def __init__(self):  # python中的初始化方法,实例化时会被自动调用
 		print "A"
 
class B(A):
 	def __init__(self):
 		super(B, self).__init__()  # 调用B的“父类”方法
 		print "B"

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

class D(B,C):
 	def __init__(self):
 		super(D, self).__init__()  # 多继承时这种写法只会调用B的__init__方法
 		print "D"

代码逻辑非常简单,每个类在被初始化的时候调用一下父类的初始化方法,然后再打印一个标记

按照正常对继承概念的理解,B会调用A,C会调用A,D会调用B,C不会被调用,每个类调用的父类是确定不变的。

下面是调用的结果

In[74]: D()
A
C
B
D
Out[74]: <__main__.D at 0x123a66250>

并不是预期的D->B->A顺序,而是D->B->C->A

我们可以分析一下这里,如果是D在调用完B后又调用了C,那么实际的调用顺序应该是D->B->A->C才对,B在完成自己的初始化方法之前不可能先退出去调用D的另一个父类初始化方法12

而B显然没有调用A,而是去调用了C,然后由C去调用的A。
事实上,并不是D在调用完B后又调用了C,而是B的super方法调用了C。
在这里,C变成了B的父类,看起来非常的匪夷所思,是个难以理解的现象。

但我们可以从另一个角度去理解这个现象:
定义class的继承是在告诉python应该用什么样的顺序调用方法
super告诉python应该调用这个顺序上哪个位置的方法

super方法其实并不是调用“父类”,只是多数时间它碰巧调到了父类而已。

MRO(Method Resolution Order) 方法解析顺序

python类当中有一个叫做mro的元信息,存储方法的解析顺序列表,也可以理解为继承列表

D.__mro__
Out[59]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

python编译器编译class时,会根据代码给出的继承关系,通过C3算法将继承树转换成方法解析顺序列表。列表会遵循下列规则3

  1. 子类永远在父类的前面
  2. 如果有多个父类,会根据它们在列表中的顺序去检查
  3. 如果对下一个类存在两种不同的合法选择,那么选择第一个父类

我们现在再来理解一下这句话:
定义class的继承是在告诉python应该用什么样的顺序调用方法


由于有上面那些规则的限制,python的多重继承也会有一些特殊情况
下面的代码和上面的基本一致,唯一的区别是将C的父类改成了B

class A(object):
 	def __init__(self):
 		print "A"
 
class B(A):
 	def __init__(self):
 		super(B, self).__init__()
 		print "B"

class C(B):
 	def __init__(self):
 		super(C, self).__init__()
 		print "C"

class D(B,C):
 	def __init__(self):
 		super(D, self).__init__()
 		print "D"

然后你会看到一个报错:

File "<ipython-input-75-e5fe715574aa>", line 18, in <module>
    class D(B, C):
TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution
order (MRO) for bases C, B

C是B的子类,但是它出现在了B的右边,违反了第一条规则:子类永远在父类的前面

再来看第一个例子中的mro信息:

D.__mro__
Out[59]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

类D在这个列表的第一位,它的super方法调用了B

super(D, self).__init__()

B的super方法调用了C

super(B, self).__init__()

C的super方法调用了A

super(C, self).__init__()

每个super方法都调用了mro上第一个入参右边那一位的方法:

  • D的右边是B
  • B的右边是C
  • C的右边是A

super方法只是在告诉python应该调用这个顺序上什么位置的方法

super方法

super本质上是个class,它的定义如下:

class super(object):

    def __init__(self, type1, type2=None): # known special case of super.__init__
        """
        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)
        Typical use to call a cooperative superclass method:
        class C(B):
            def meth(self, arg):
                super(C, self).meth(arg)
        # (copied from class doc)
        """
        pass

我们只讨论注释中的第一种用法

super会绑定obj对象的mro列表中type右边的那个类,然后再对绑定的类对象进行操作
相当于实例化一个super类,但是得到的是obj的某个父类对象的代理,对这个super对象操作就是对那个父类对象操作。

在上面这个机制中,下面这个骚操作在python中是合法的:

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

class B(A):
  	def __init__(self):
  		super(B, self).__init__()
  		print "B"

class S(object):
   	def __init__(self):
   		super(S, self).__init__()  # 不会执行
   		print "S"

class C(S):
  	def __init__(self):
  		super(B, self).__init__()  # 骚操作,这里C的父类是S,但是传入的参数是B
  		print "C"

class D(C,B):
  	def __init__(self):
  		super(D, self).__init__()
  		print "D"

MRO信息:

In[83]: D.mro()
Out[83]: [__main__.D, __main__.C, __main__.S, __main__.B, __main__.A, object]

调用结果:

In[84]: D()
A
C
D
Out[84]: <__main__.D at 0x123a74a50>

class C的super调用了一个根本不在自己继承树上的B的父类A,而在mro列表中,B存在于C的右边,因此这种调用方式是合法的,最后的输出也能看出来,C跳过了自己的父类S和无关联的类B,直接调用了类B的父类A,S的初始化方法并没有执行。
实际调用流程:

  1. D初始化,通过super(D, self)语句找到mro列表中D右边的那个类(也就是C)并绑定
  2. 通过super(D, self).__init__()调用C的初始化方法
  3. C初始化,通过super(B, self)语句找到mro列表中B右边的那个类(也就是A)并绑定
  4. 通过super(B, self).__init__()调用A的初始化方法
  5. A初始化,通过super(A, self)语句找到mro列表中A右边的那个类(也就是object)并绑定
  6. 通过super(A, self).__init__()调用object的初始化方法

通过上面的流程可以看出,super实际上并不关心继承关系,它只是在找mro上指定类的右边是哪个类。哪怕两个类毫无关系,只要他们出现在同一个mro列表上,就可以从左向右的调用

虽然C并没有调用自己的父类S,但他们仍然存在于类D的mro列表中。是否调用并不影响mro解析顺序

在这个机制中,“父类”的信息像是一个环境信息,由上下文来决定,不同的上下文可能会产生不同的执行顺序和行为结果。
如果不用“父类”这个概念来理解super方法,这个现象就不会令人疑惑。

super就是一个通过入参的mro列表寻找指定类右边的类并进行绑定的class,不是关键字,也没有任何特殊的处理逻辑。你也可以在对象之外使用super

In[100]: d=D()
A
C
D

In[101]: super(C,d).__init__()
S

In[102]: super(S,d).__init__()
A
B

当然,python会保存真正的继承关系信息,如果希望知道某个子类是否继承了某个类,可以用issubclass内置函数来判断

In[102]: issubclass(D,C)
Out[102]: True

In[103]: issubclass(C,B)
Out[103]: False

  1. 如果你觉得这样可以,那可能需要重新理解一下面向对象的相关概念。 ↩︎
  2. 一点小补充:在正常的继承关系中,对父类的初始化是子类初始化的一部分。没有完成父类初始化的对象本身是未完成初始化的,是“残缺”的;只完成父类初始化却不完成本身的初始化逻辑的,也是未完成初始化的“残缺对象”。理智的语言设计者是不会允许这种危险操作的4。 ↩︎
  3. 简书:python之理解super及MRO列表 ↩︎
  4. java会在对象完成全部初始化之前就返回对象引用,这个情况是有区别的。java的机制会确保对象在使用前一定完成初始化,不会在初始化过程中让出执行权、主动暂停初始化过程去执行其他逻辑(中断和异常除外,垃圾回收会使用单独的线程) ↩︎