# MRO和super

# 类的多重继承-菱形继承-C3(类广度)

Python中的MRO(方法解析顺序)。在没有多重继承的情况下,向对象发出一个消息,如果对象没有对应的方法,那么向上(父类)搜索的顺序是非常清晰的。如果向上追溯到object​类(所有类的父类)都没有找到对应的方法,那么将会引发AttributeError​异常。

但是当出现多重继承尤其是菱形继承的时候,向上追溯到底应该找到那个方法就得确定MRO。Python 3中的类以及Python 2中的新式类使用C3算法 (opens new window)来确定MRO,它是一种类似于广度优先搜索的方法;Python 2中的旧式类(经典类)使用深度优先搜索来确定MRO。在搞不清楚MRO的情况下,可以使用类的mro​方法或__mro__​属性来获得类的MRO列表。

新式类可以直接通过 类名.__mro__​ 的方式获取类的 MRO​,也可以通过 类名.mro()​ 的形式,旧式类是没有 mro 属性和 mro() 方法的。

方法解析顺序 Method Resolution Order​,简称 MRO​。主要用于在多继承时判断方法,属性的调用路径。

  • 在搜索方法时,是按照 mro()​ 输出的结果,从左到右的顺序查找

  • 如果找到,在当前类中找到方法就直接执行,不在搜索

  • 没有找到,就依次查找下一个类中是否有对应的方法,找到执行,不在搜索

  • 如果最后一个类,还没有找到方法,程序报错

  • 可以通过super​方法来调用父类的函数,其被设计用来解决多重继承问题

    • 单继承中与父类名调用无区别

    • 多重继承中能保证父类名只调用一次

      父类名调用则还是会调用多次

    • 在python2.x中,函数super()需要两个实参:子类名和对象self。为帮助python将父类和子类联系起来,这两个参数必不可少。

    • 在python3.x中,函数super()可以不携带实参-解释器会自动将当前类和self传入。如果携带了参数,例如super(B, self)​中self指向B则是指找到self的mro表中B的下一个类

# encoding: utf-8
class Parent(object):
    def eat(self):
        print("\tparent --- 爱吃饭")

class Son1(Parent):
    def eat(self):
        print("son1 --- eat()")
        super().eat()
        print("\tson1  ---  爱吃蔬菜\n")

class Son2(Parent):
    def eat(self):
        print("son2 --- eat()")
        super().eat()
        print("\tson2  ---  爱吃水果\n")

class Grandson(Son1, Son2):
    def eat(self):
        print("grandson --- eat()")
        super().eat()
        print("\tgrandson --- 爱吃零食")
  
    def eat2(self):
        return super(Son1, self).eat()

if __name__ == '__main__':
    g = Grandson()
    print(Grandson.__mro__) # (<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <type 'object'>)

    g.eat()
    """
    grandson --- eat()
    son1 --- eat()
    son2 --- eat()
            parent --- 爱吃饭
            son2  ---  爱吃水果

            son1  ---  爱吃蔬菜

            grandson --- 爱吃零食
    """
    # g.eat2()
    """
    son2 --- eat()
        parent --- 爱吃饭
        son2  ---  爱吃水果
    """


# super

注意到super​是有四种用法的。

super()​与super(__class__,self)​等价。

super(type, instance)​用于调用instance​继承链上位于type​后的下一个类的同名方法。

super(type, type2)​用于调用type2​类在type​的继承链上的下一个类(MRO中的下一个类)的同名方法。在使用super(type, type2)​时,首先需要确保type2​是type​的子类,否则会抛出TypeError​异常。

class super(object)
 |  super() -> same as super(__class__, <first argument>)
 |  super(type) -> unbound super object
 |  super(type, obj) -> bound super object; requires isinstance(obj, type)
 |  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().meth(arg)
 |  This works for class methods too:
 |  class C(B):
 |      @classmethod
 |      def cmeth(cls, arg):
 |          super().cmeth(arg)

# MRO例题

# 题目:阅读下面的代码说出运行结果。

class A:
    def who(self):
        print('A', end='')

class B(A):
    def who(self):
        super(B, self).who()
        print('B', end='')

class C(A):
    def who(self):
        super(C, self).who()
        print('C', end='')

class D(B, C):
    def who(self):
        super(D, self).who()
        print('D', end='')

item = D()
item.who()

点评:这道题考查到了两个知识点:

  1. Python中的MRO(方法解析顺序)。在没有多重继承的情况下,向对象发出一个消息,如果对象没有对应的方法,那么向上(父类)搜索的顺序是非常清晰的。如果向上追溯到object​类(所有类的父类)都没有找到对应的方法,那么将会引发AttributeError​异常。但是有多重继承尤其是出现菱形继承(钻石继承)的时候,向上追溯到底应该找到那个方法就得确定MRO。Python 3中的类以及Python 2中的新式类使用C3算法 (opens new window)来确定MRO,它是一种类似于广度优先搜索的方法;Python 2中的旧式类(经典类)使用深度优先搜索来确定MRO。在搞不清楚MRO的情况下,可以使用类的mro​方法或__mro__​属性来获得类的MRO列表。
  2. super()​函数的使用。在使用super​函数时,可以通过super(类型, 对象)​来指定对哪个对象以哪个类为起点向上搜索父类方法。所以上面B​类代码中的super(B, self).who()​表示以B类为起点,向上搜索self​(D类对象)的who​方法,所以会找到C​类中的who​方法,因为D​类对象的MRO列表是D --> B --> C --> A --> object​。
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
ACBD