- 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:
- 子类永远在父类的前面
- 如果有多个父类,会根据它们在列表中的顺序去检查
- 如果对下一个类存在两种不同的合法选择,那么选择第一个父类
我们现在再来理解一下这句话:
定义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的初始化方法并没有执行。
实际调用流程:
- D初始化,通过super(D, self)语句找到mro列表中D右边的那个类(也就是C)并绑定
- 通过super(D, self).__init__()调用C的初始化方法
- C初始化,通过super(B, self)语句找到mro列表中B右边的那个类(也就是A)并绑定
- 通过super(B, self).__init__()调用A的初始化方法
- A初始化,通过super(A, self)语句找到mro列表中A右边的那个类(也就是object)并绑定
- 通过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
- 如果你觉得这样可以,那可能需要重新理解一下面向对象的相关概念。 ↩︎
- 一点小补充:在正常的继承关系中,对父类的初始化是子类初始化的一部分。没有完成父类初始化的对象本身是未完成初始化的,是“残缺”的;只完成父类初始化却不完成本身的初始化逻辑的,也是未完成初始化的“残缺对象”。理智的语言设计者是不会允许这种危险操作的4。 ↩︎
- 简书:python之理解super及MRO列表 ↩︎
- java会在对象完成全部初始化之前就返回对象引用,这个情况是有区别的。java的机制会确保对象在使用前一定完成初始化,不会在初始化过程中让出执行权、主动暂停初始化过程去执行其他逻辑(中断和异常除外,垃圾回收会使用单独的线程) ↩︎