python允许子类继承多个基类(父类),这种特性就是通常所说的多重继承,但是在多重继承中,如何正确找到没有在当前类(或者当前子类)定义的属性。在使用多重继承的时候,有两个不同的方面要注意;1:要找到合适的属性,2:在重写一个方法的时候,如何调用对应的父类方法以发挥他们的作用,同时在子类中处理好自己的义务。
方法解释顺序(MRO: Method Resolution Order)
- 在Python2.2之前,算法非常简单,使用的是深度优先算法,即根据继承基类的位置信息,从左到右进行搜索,取得在子类中使用的属性,使用第一次找到的名字。
但是在目前的版本中,这种算法就会出现问题,后续会介绍,所以新的查询方法不再是深度优先,而是广度优先算法 -
'''父类1''' class P1(object): def foo(self): print("called P1-foo()") '''父类2''' class P2(object): def foo(self): print("called P2-foo()") def bar(self): print("called P2-bar()") '''子类1, 从P1,P2派生''' class C1(P1, P2): pass '''子类2,从P1, P2 派生''' class C2(P1, P2): def bar(self): print("called C2-bar()") '''定义子孙类, 从C1,C2派生''' class GC(C1, C2): pass
-
根据上述代码,从该图中可以看到父类,子类以及子孙类的关系,P1中定义了foo(),P2中定义了foo()和bar(),C2中定义了bar()。在经典类和新式类(目前使用的版本)中行为是不同的。
经典类和新式类在MRO的不同
经典类
- 在经典类中,通过交互式解释器中执行上面的声明,可以验证经典类的解释顺序是深度优先,从左至右:
gc = GC() gc.foo() # GC=> C1 => P1 输出是: called P1-foo() gc.bar() # GC => C1 => P1 => P2 输出是: called P2-bar()
即当调用foo()的时候,它首先在当前类(GC)中查找,如果没有找到,就向上查找最亲的父类C1, 查找未遂,继续沿着树向上查找父类P1,直到找到foo()。
同样,对于bar(),它通过搜索GC, C1,P1,然后在P2中找到该方法,因为使用这种解释顺序的缘故,C2.bar()根本就不会被搜索到。
所有当想调用C2的bar()方法的时候只能通过类,采用非绑定方式去调用:C2.bar(gc)
新式类(目前使用的版本)
- 新式类的MRO方法有所不同
- 即在执行gc.foo()的时候,查找顺序是:GC =>C1 =>C2 =>P1
z在执行gc.bar()的时候,查找顺序是: GC=> C1 => C2
与经典类沿着树一步步的上溯不同,新式类,首先查找的是同胞兄弟,采用一种广度优先的算法,当查找foo(),它检查GC,然后是C1,C2, 然后在P1中找到,如果P1中没有,查找会到达P2, foo()的底线是,包括经典类和新式类都会在P1中找到,虽然同归,但却殊途!
然而bar()的结果是不同的,它首先搜索C1,C2,紧接着在C2中找到了,这样就不会再继续搜索器祖父类P1和P2。 这种情况的解释方式更适合那种要查找GC更亲近的bar()方案,当然,如果要调用上一级,只要按照前述方法,使用非绑定的方式去查找就可以。 - 新式类也有一个__mro__属性,告诉查找的顺序