zoukankan      html  css  js  c++  java
  • Python:多重继承 和 MRO顺序(C3算法)

    python存在多重继承机制,但是先说:尽量不要用多重继承。

    有点多,慢慢看。。。

    目录:

    1. 讲多重继承前,先看看:每一代都是单继承的继承问题

    2. 子类访问父类 —— super方法

    3. 多重继承 --- 非super

    4. 多重继承 --- super

    5. MRO顺序 --- C3算法

    # -------------------------------------------------------------------------------------

    • 讲多重继承前,先看看:每一代都是单继承的继承问题
    class grandfather(object):
        ''' grandfather类的定义如下'''
        con_flag = 1   #普通静态字段
        __con_data = '爷爷'  #私有静态变量只能在类内使用
        def __init__(self,name,age,id_):
            self.name = name
            self.age = age
            self.__id = id_
            print('grandfather类初始化完成')
            
        def __get_age(self):   #私有函数只能在类内使用
            return self.age
        
        def show_age(self):
            print(self.age)
            print('grandfather年龄显示完毕')
    
    class father(grandfather):
        ''' father类的定义如下'''
        con_flag = 2   #普通静态字段
        __con_data = '爸爸'  #私有静态变量只能在类内使用
    
    class son(father):
        ''' son类的定义如下'''
        con_flag = 4   #普通静态字段
        __con_data = '儿子'  #私有静态变量只能在类内使用
    
    if __name__ == '__main__':
        son1 = son('Tom',10,'001')  #实例化时,会依次查找上层父类的__init__函数,注意这个函数并不一定要有
        son1.show_age()   #子类中有该方法,则会直接执行;如果没有才去上层父类,每次单继承时这个很清晰

    可见:

    (1)son类继承自father类,而father类继承自grandfather类;所以son的实例对象也拥有了grandfather类的方法和属性;只不过属性/方法名称相同时,子类的属性/方法会覆盖父类。

    (2)当对象调用某方法时,先在子类(本类)中查找,找不到再向上查找父类,一直向上直到找到第一个该方法出现的类。

    • 子类访问父类 —— super方法

    有时子类中重写了某些方法或者属性,但是又想要使用父类的方法或者属性;或者子类单纯的想使用父类的方法或属性,可以用super方法。让我们稍微改变一下上述代码:

    class grandfather(object):
        ''' grandfather类的定义如下'''
        con_flag = 1   #普通静态字段
        __con_data = '爷爷'  #私有静态变量只能在类内使用
        def __init__(self,name,age,id_):
            self.name = name
            self.age = age
            self.__id = id_
            print('grandfather类初始化完成')
            
        def __get_age(self):   #私有函数只能在类内使用
            return self.age
        
        def show_age(self):
            print(self.age)
            print('grandfather年龄显示完毕')
    
    class father(grandfather):
        ''' father类的定义如下'''
        con_flag = 2   #普通静态字段
        __con_data = '爸爸'  #私有静态变量只能在类内使用
    
    class son(father):
        ''' son类的定义如下'''
        con_flag = 4   #普通静态字段
        __con_data = '儿子'  #私有静态变量只能在类内使用
    
        def show_age(self):
          super(son, self).show_age()  # 显式表示调用某个类的超类,此时father类并没有show_age方法,所以继续向上查找超类,找到grandfather的方法
          super().show_age()          # super().show_age() 默认指的是当前类son的超类,用于隐式指代父类,而不用提供父类名称
          grandfather.show_age(self)  #此时必须提供self参数
         print(super().con_flag) #父类father中有该属性,于是不会继续向上查找grandfather类了
         print('son年龄显示完毕')
    if __name__ == '__main__': son1 = son('Tom',10,'001') son1.show_age() #子类中有该方法,则会直接执行;如果没有才去上层,每次单继承时这个很清晰 print(son1.con_flag)

    可见:

    (1)子类中调用父类(多层回溯的父类)方法或属性的方式有两种:

    第一种是 super().func_name() 或者 super().attr_name:例如super().show_age() 、super().con_flag;super(son, self).show_age() 这种形式可以显示指示是调用哪个类的超类 —— 可以确保多重继承时父类的方法只被执行一次。

    第二种是 父类名.父类方法(self) 或者 父类名.父类属性:注意此时方法中self参数是必须的 —— 会导致多重继承时父类中的方法被多次执行,所以多重继承时最好用super方式,但是最好不要用多重继承

    (2)搜索方式是:单继承比较简单,简言之就是直接依次向上层父类搜索;这个用C3算法(MRO Method Resolution Order)也可以算,后面介绍该算法

    (3)super方法意义:

    单继承:可以不需要父类名称就可以调用父类方法;因为父类的名称可能会变化或者调用其他父类。

    多重继承:用于确保各父类只被搜索、调用一次。

    (4)super原理

    super(class_name, instance),它所做的事情是:

    首先,获取instance的MRO顺序表:instance.__class__.mro(),例如上面的 son1.__class__.mro();

    其次,查找class_name在当前MRO列表中的index,然后在instance的MRO列表上搜索class_name类的下一个类。

    总结就是:super(class_name, instance)用于在 instance 的 MRO 列表上搜索 class_name类 的下一个类。

    • 多重继承 --- 非super
    class grandfather(object):
        ''' grandfather类的定义如下'''
        con_flag = 1   #普通静态字段
        __con_data = '爷爷'  #私有静态变量只能在类内使用
        def __init__(self,name,age,id_):
            self.name = name
            self.age = age
            self.__id = id_
            print('grandfather类初始化完成')
        
        def show_age(self):
            print(self.age)
            print('grandfather年龄显示完毕')
    
    class father(grandfather):
        ''' father类的定义如下'''
        con_flag = 2   #普通静态字段
        __con_data = '爸爸'  #私有静态变量只能在类内使用
        
        def show_age(self):
            grandfather.show_age(self)   #调用父类的方法
            print('father年龄显示完毕')
    
    class aunt(grandfather):
        ''' aunt类的定义如下'''
        con_flag = 3   #普通静态字段
        __con_data = '姑姑'  #私有静态变量只能在类内使用
    
        def show_age(self):
            grandfather.show_age(self)   #调用父类的方法
            print('aunt年龄显示完毕')
    
    class son(father,aunt):
        ''' son类的定义如下'''
        con_flag = 4   #普通静态字段
        __con_data = '儿子'  #私有静态变量只能在类内使用
    
        def show_age(self):
            father.show_age(self)   #子类调用父类的方法
            aunt.show_age(self)     #子类调用父类的方法
            print('son年龄显示完毕')
    
    if __name__ == '__main__':
        son1 = son('Tom',10,'001')
        son1.show_age()   

    可见:

    (1)子类son调用父类father和aunt的时候,调用了2次基类grandfather的方法(有两次:grandfather年龄显示完毕);如果中间的继承关系更复杂,那么会显得更难以理解。

    (2)这里的调用父类方法的方式是上述第二种,即:父类名.父类方法(self),而不是super那一种。

    正因为如此,所以super方法的使用在多重继承里面更有意义。

    • 多重继承 --- super
    class grandfather(object):
        ''' grandfather类的定义如下'''
        con_flag = 1   #普通静态字段
        __con_data = '爷爷'  #私有静态变量只能在类内使用
        def __init__(self,name,age,id_):
            self.name = name
            self.age = age
            self.__id = id_
            print('grandfather类初始化完成')
        
        def show_age(self):
            print(self.age)
            print('grandfather年龄显示完毕')
            return self.con_flag
    
    class father(grandfather):
        ''' father类的定义如下'''
        con_flag = 2   #普通静态字段
        __con_data = '爸爸'  #私有静态变量只能在类内使用
    
        def show_age(self):
            s1 = super().show_age() 
            print('father年龄显示完毕')
            return s1
    
    class aunt(grandfather):
        ''' aunt类的定义如下'''
        con_flag = 3   #普通静态字段
        __con_data = '姑姑'  #私有静态变量只能在类内使用
    
        def show_age(self):
            s2 = super().show_age() 
            print('aunt年龄显示完毕')
            return s2
    
    class son(father,aunt):
        ''' son类的定义如下'''
        __con_data = '儿子'  #私有静态变量只能在类内使用
    
        def show_age(self):
            ss = super().show_age() 
            print('son年龄显示完毕')
            print(ss)
    
    if __name__ == '__main__':
        son1 = son('Tom',10,'001')
        son1.show_age()   #子类中有该方法,则会直接执行;如果没有才去下一个类查找
        print(son1._son__con_data)   #本身有这个参数,所以不用回溯查找
        print(son1.__class__.mro())  #查看MRO顺序

    可见:

    (1)用super时,基类grandfather类只访问了一次。

    (2)相同属性名称时,下层属性名(方法)会覆盖下一个类的属性名(或方法),例如con_flag属性,通过MRO顺序,son1对象获取的是father类的该属性con_flag;例如__con_data属性,在本类(son类)中存在,所以会覆盖下一个类的属性。

    (3)类的搜索顺序是:[<class '__main__.son'>, <class '__main__.father'>, <class '__main__.aunt'>, <class '__main__.grandfather'>, <class 'object'>],这是通过C3算法计算出来的MRO顺序列表。

    (4)之所以说下一个类而不说父类,是因为这个顺序是C3算法计算的,不是严格的继承顺序。

    (5)由MRO顺序,解析一下son实例化对象son1所包含的信息:

    (6)依据son1对象的信息,分析son1.show_age()的执行过程:

    • MRO顺序 --- C3算法

    python官网上有详细解释,这里稍微展开一下:

    1. 首先 (C1C2C3...Cn)表示一个多重继承序列,注意这个顺序很重要

    2. 则 head(头)=C1,tail(尾)=(C2C3...Cn),即除了第一个类属于head之外,其他的全属于tail

    3. 使用 C+(C1C2C3...Cn) = CC1C2...Cn 表示类序列的和;

    4. 那么,类C的线型查找序列公式就是类C加上父类的线型查找序列和父类的线型序列的混合merge,符号表示就是:L[C(C1C2...Cn)] = C + merge(L[C1], ... ,L(Cn), C1...Cn);

    5. 如果C没有父类,则L[C] = C

    其规则就是:取第一个类序列的head,例如CC1C2...Cn的head就是C,如果这个head不在任何其他序列的tail里面,就把这个head加入到查找序列里面 —— 认为这是一个好head,并且从merge表达式里面去掉这个类;否则的话,就取第二个类序列,判断这个类序列的head是否合格,如果是个好head,则同样加入到查找序列,否则,继续对下一个类序列判断;直到所有类class都在merge里面被去掉,也就是全部进入查找序列;如果最后还是存在类class不能进去查找序列,则返回Exception。

    示例展示:

    首先类的继承关系如下:

    O = object类
    class F(O): pass
    class E(O): pass
    class D(O): pass
    class C(D,F): pass
    class B(D,E): pass
    class A(B,C): pass

    我们要算的是A的MRO顺序就是:

    L[A(B(D(O),E(O)),C(D(O),F(O)))] = A + merge(L[B(D(O),E(O))], L[C(D(O),F(O))], B(D(O),E(O)) C(D(O),F(O))),这个就是根据上面的类C的线型查找序列公式

    • 简化一下就是 L[A] = A + merge(L[B], L[C], BC) 

    L[B] = L[B(D(O),E(O))]  = B + merge(L[D], L[E], DE)

    L[C] = L[C(D(O),F(O))] = C + merge(L[D], L[F], DF)

    • 然后继续划分:

    L[D] = L[D(O)] = D + merge(L[O], O) ,由于O本身没有父类,所以L[O] = O,所以 L[D] = D + merge(O, O) = D + O =DO ----> merge(O, O)表达式中,O都在第一个位置,也就是head,所以O可以放入L[D]的查找序列中了

    同理:L[E] = L[E(O)] = E + merge(L[O], O) = E + O =EO,L[F] = FO

    • 所以得到:

    L[B] = B + merge(DO, EO, DE),由于此时D不是任何merge序列的tail(因为每次先从merge第一个序列开始,这里第一个就是DO序列,而DO序列的head就是D),所以D可以提出来放入B的查找序列中,并在merge中去掉D,即:L[B] = B + D +merge(O, EO, E) ,同理先判断O,由于O在EO序列的tail,所以跳到下一个序列EO,而EO序列的head是E,此时E不在任何merge序列的tail,所以E可以提出来,得到 L[B] = B + D + E +merge(O, O) = BDEO。

    同理,L[C] = C + merge(DO, FO, DF) = C + D + F + O = CDFO

    • 代入L[A]得到:

    L[A] = A + merge(BDEO, CDFO, BC) ,先判断BDEO的head即B,此时DEO为tail,发现B不在任何merge序列的tail,所以提出来得到:L[A] = A + B + merge(DEO, CDFO, C);

    判断DEO的head即D,由于D在CDFO的tail,所以跳过,判断CDFO的head即C,发现满足条件,所以提出C,得到:L[A] = A + B + C + merge(DEO, DFO);

    同理一次判断,得到:L[A] = A + B + C + D + merge(EO, FO) = A + B + C + D + E + F + O = ABCDEFO。

    • 所以类A的MRO查找序列就是ABCDEFO。

    但是!!!!

    MRO序列计算也有得不到想要的结果,也就是返回Exception,原文中有个例子:

    >>> O = object
    >>> class X(O): pass
    >>> class Y(O): pass
    >>> class A(X,Y): pass
    >>> class B(Y,X): pass
    class C(A, B): pass

    L[C] = C + merge(L[A], L[B], AB) = C + merge(A + merge(L[X], L[Y], XY), B + merge(L[Y], L[X], YX), AB) = C + merge(A+merge(XO, YO, XY), B+merge(YO, XO, YX), AB) = C + merge(A+XYO, B+YXO)  = C + merge(AXYO, BYXO) = C + A + B + merge(XYO, YXO)

    此时,merge(XYO, YXO)中的X和Y既是head又是tail,已经无法优化合并,所以会报错,Exception。

    注意:以上计算中,每个继承关系中,类的继承排列顺序很重要

    比较复杂吧。。。所以不必要时最好不要多重继承;例如可以用在子类中分别实例化父类来作为子类的属性,这样就可以不用多继承来实现调用父类的方法,可参考:https://baijiahao.baidu.com/s?id=1660242196992022960&wfr=spider&for=pc

    例如:

    # 在son中写入:可以将实例化对象作为属性放入需要的位置
    self.aunt_ = aunt()
    self.father_ = father()

    ##

    参考:

    https://www.cnblogs.com/szy13037-5/articles/9562639.html

    https://www.cnblogs.com/silencestorm/p/8404046.html

    https://blog.csdn.net/sdzhr/article/details/81084112

    https://blog.51cto.com/freshair/2063290

    https://www.jianshu.com/p/e188daac678c

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

    https://blog.csdn.net/jonstank2013/article/details/50754834

    https://blog.csdn.net/zzsfqiuyigui/article/details/61672631

    https://www.cnblogs.com/szy13037-5/articles/9562639.html

  • 相关阅读:
    C# 还原SQL数据库(非存储过程方式)
    C# 无边框窗体移动代码
    SQL 2008 R2 数据库镜像操作
    序列号
    Oracle VM VirtualBox 随系统自动启动虚拟机的方法
    SQL每个用户最后的一条记录
    JS判断是否在微信浏览器打开
    使用device.js检测设备并实现不同设备展示不同网页
    check单选框多个全选与取消全选
    判断滚动是否到达底部
  • 原文地址:https://www.cnblogs.com/qi-yuan-008/p/12845569.html
Copyright © 2011-2022 走看看