MRO和C3算法

发布时间:2019-04-01 21:53:49编辑:auto阅读(2179)

    本节主要内容:

    1.python多继承

    2.python经典类的MRO

    3.python新式类的MRO,C3算法

    4.super()

     

    一.python多继承

    在python中类与类之间可以有继承关系,这也是面向对象的一大特征之一.

    在继承关系中,子类自动拥有父类中除了私有属性外的其他所有内容.python支持多继承.一个类可以拥有多个父类.

     

    class Shen:
        def fly(self):
            print("大神会飞")
    class Hou:
        def chi(self):
            print("猴子吃桃子")
    
    class SunWuKong(Shen, Hou): # 一个类可以继承多个无关的类. 一个类可以被多个无关的类继承
        pass
    
    class TaiShangLaoJun(Shen):
        pass
    
    
    swk = SunWuKong()
    swk.fly()
    swk.chi()

    此时,孙悟空是一只猴子,同时也是一个神仙.那孙悟空继承了这两个类.孙悟空自然就可以执行这两个类的方法.

    在多继承中存在着这样一个问题.当两个父类中出现了重名方法的时候.该执行哪一个呢?这时就设计到如何查找父类方法的

    这么一个问题.即MRO(method resolution order)问题. 在python中这时一个很复杂的问题.因为在不同的python版本中使用的

    是不同的算法来完成MRO的.首先,我们目前能见到的两个版本:

    ·python2

    在python2中存在两种类.

    一个叫经典类.在python2.2之前使用的是经典类.经典类在基类的根如果什么都不写,表示继承xxx.

    一个叫新式类.在python2.2之后出现了新式类.新式类的特点是基类的根是object

    ·python

    python3中使用的都是新式类,如果基类谁都不继承,那这个类会默认继承object

     

    二.经典类的MRO

    经典类的MRO是通过树形结构的深度优先遍历.

    在python的继承体系中,我们可以把类与类继承关系化成一个树形结构的图.

    class A:
        pass
    
    class B(A):
        pass
    
    class C(A):
        pass
    
    class D(B, C):
        pass
    
    class E:
        pass
    
    class F(D, E):
        pass
    
    class G(F, D):
         pass
    
    class H:
        pass
    
    class Foo(H, G):
        pass

     

    对付这样的MRO.很简单.画图即可:

    继承关系图已经有了.那如何进行查找呢?记住一个原则.在经典类中采用的是树形深度优先遍历方案.就是从下往上,从左子树

    到右子树,一条路走到头.

    所以上面的类的MRO为:Foo->H->G->F->D->B->A->C->E

    三.新式类的MRO

    python中的新式类的MRO采用的是C3算法来完成的.

    C3算法不需要画图,我们只要看代码就可以了:

    class A:
        pass
    class B(A):
        pass
    class C(A):
        pass
    class D(B, C):
        pass
    class E(C, A):
        pass
    class F(D, E):
        pass
    class M(F, E):
        pass
    class N:
        pass
    class P(M,N):
        pass
    class G(P):
        pass
    class O:
        pass
    class H(G, F):
        pass

     

    首先.我们要确定从H开始找,也就是说.创建的是H的对象.

    如果从H找,那找到H+H的父类的C3,我们设C3算法是L(x),即给出x类.找到的MRO

    L(H) = H + L(G) + L(F) + (G,F)    

    继续从代码中找G和F的父类往里面代

    L(G) = G + L(E) +  (E,)       

    L(F) = F + L(D)+ L(E) + (D,E)   

    继续找E 和 D       

    L(E) = E + L(C) + L(A) +(C,A)       

    L(D) = D + L(B) + L(C) + (B,C)

    继续找B和C       

    L(B) = B + L(A) +  (A,)      

    L(C) = C + L(A) + (A,)

    最后就剩下一个A了,因为A没有父类所以不用再找了.接下来把L(A)往里面代,再推回去,但要记住,

    这里的+表示的是merge.merge的原则是用每个院的头一项和后面元组的除头一项外的其他元素

    进行比较,看是否存在.如果存在,就从下一个元组的头一项继续找,如果找不到,就拿出来.作为merge

    的结果的一项.以此类推,直到元组之间的元素都相同了,也就不用再找了.

    L(B) =(B,) + (A,) +(A,) -> (B, A)       

    L(C) =(C,) + (A,) + (A,) -> (C, A)   

    L(E) = (E,) + (C, A) + (A) +(C,A)  -> E, C, A       

    L(D) = (D,) + (B, A) + (C, A) + (B,C)  -> D, B, A 

    L(G) = (G,) + (E, C, A) -> G, E, C, A       

    L(F) = (F,) + (D, B, A) + (E, C, A) + (D,E) -> F,  D, B,  E, C, A   

    L(H) = (H, ) + (G, E, C, A) + ( F,  D,  B,  E, C, A) + (G,F)-> H, G, F, D, B, E, C, A 

    最终的结果是HGFDBECA,那如何验证?其实python中可以使用类名.__mro__获取到类的MRO信息.

    print(H.__mro__)
    # 结果:
    # (<class '__main__.Foo'>, <class '__main__.H'>, <class '__main__.G'>, <class '__main__.F'>, 
    # <class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, 
    # <class '__main__.E'>, <class 'object'>)

     

    C3是把我们多个类产生的共同继承留到最后去找,所以,我们也可以从图上来看到相关的规律.这个要多写多画图

    才能感觉到.但是如果没有所谓的共同继承关系,那就几乎就当成深度遍历就可以了.

    以下是python官网关于MRO C3算法的文档地址:

    https://www.python.org/download/releases/2.3/mro/

    四.super()

    super()可以帮助我们执行MRO中下一个父类的方法.通常super()有两个使用的地方:

    1.可以访问父类的构造方法

    2.当子类方法想调用父类(MRO)中的方法

    先看第一种情况:

    class Foo:
        def __init__(self,a,b,c):
            self.a = a
            self.b = b
            self.c = c
    
    class Bar(Foo):
        def __init__(self,a,b,c,d):
            super(Bar, self).__init__(a,b,c)
            self.d = d
    
    b = Bar(1,2,3,4)
    print(b.__dict__)
    
    # 结果:
    # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

     

    这样我们就不需要写这么多代码,直接到父类的构造帮我们完成一部分代码

     

    第二种:

    class ShengWu:
        def dong(self): # 实例方法
            print(self)
            print("我是生物")
    
    class Animal(ShengWu):
       def dong(self):
            print("我是动物")
    class Cat(Animal):
        def dong(self): # 子类中出现了和父类重名的内容. 表示对父类的方法的覆盖(重写). 半盖(java)
            super(Cat, self).dong()
            # super(Animal, self).dong() # 定位到Animal. 找Animal的下一个
            # super(类, 对象).方法()  找到MRO中的类. 找这个类的下一个. 去执行方法
            print("我的猫也会动")

     

     

    最后是一个面试题:

    # MRO + super ⾯试题
    class Init(object):
        def __init__(self, v):
            print("init")
            self.val = v # 2
    class Add2(Init):
        def __init__(self, val): # 2
            print("Add2")
            super(Add2, self).__init__(val)
            print(self.val) # 5.0
            self.val += 2 # 7.0
    class Mult(Init):
        def __init__(self, val):
            print("Mult")
            super(Mult, self).__init__(val)
            self.val *= 5 # 5.0
    class HaHa(Init):
        def __init__(self, val):
            print("哈哈")
            super(HaHa, self).__init__(val)
            self.val /= 5   # 1.0
    class Pro(Add2,Mult,HaHa): #
        pass
    class Incr(Pro):
        def __init__(self, val): # 5
            super(Incr, self).__init__(val)
            self.val += 1 # 8.0
    
            # Incr, pro, add2, mult, haha, Init
            p = Incr(5)
            print(p.val)  # ?
            # Add2 init
            c = Add2(2)
            print(c.val)  # ?
    # 结果:
            '''
            add2
            Mult
            哈哈
            init
            5.0
            8.0
            Add2
            init
            2
            4
            
            '''

     

关键字