美文网首页
Python类-多继承和MRO, since 2022-05-1

Python类-多继承和MRO, since 2022-05-1

作者: Mc杰夫 | 来源:发表于2022-05-18 14:03 被阅读0次

    (2022.05.18 Wed)
    一个Python类可以继承多个类。继承类过程中的方法解析顺序(method resolution order,即超类中有不同的同名方法时继承的顺序)可由内置的__mro__方法查询。注意,类有该__mro__属性/方法,而实例化的结果没有__mro__。比如下面这个类继承。

    class a:
        pass
    class b:
        pass
    class c:
        pass
    class d(a,b):
        pass
    class f(c):
        pass
    class g(b):
        pass
    class k(d, f, g):
        pass
    
    >> k.__mro__
    (__main__.k,
     __main__.d,
     __main__.a,
     __main__.f,
     __main__.c,
     __main__.g,
     __main__.b,
     object)
    

    古典类和新式类

    在Python 2.x中,类的创建分为古典类和新式类。古典类不从object类继承,新式类继承object类。

    # 这是经典类
    class ac:
        pass
    # 这是新式类
    class ac(object):
        pass
    

    在Python 3.x中,取消了古典类,所有类的创建都是新式类,也就是下面三种创建类的方式等价

    class ac:
        pass
    class ac(object):
        pass
    class ac():
        pass
    

    古典类无法使用super()方法,而新式类在调用超类时除了可以用superclassname.method()的方式,还可以用super()方式调用超类方法。

    MRO method resolution order

    a b c d四个类,呈菱形继承关系,即b->ac->ad->b,c,其定义如下

    class a:
        def show(self):
            print('a show')
    class b(a):
        pass
    class c(a):
        def show(self):
            print('c show')
    class d(b, c):
        pass
    

    类继承顺序采用DFS,即从d类开始按照继承类的顺序做深度优先搜索,d \rightarrow b\rightarrow a\rightarrow object \rightarrow c\rightarrow a\rightarrow object。这样就将一个复杂的继承关系转化为一个线性继承关系。在Python 2.2以前的古典类中,继承来自于MRO中第一次出现该超类的位置。这个案例中的类继承顺序是d \rightarrow b \rightarrow a\rightarrow c

    这种继承顺序在2.2版本之后被摒弃,仍然是从左到右的顺序,如果调用的方法出现重复类,则只保留该类最后一次出现的位置。考虑上面的案例,考虑到MRO,d \rightarrow b\rightarrow a\rightarrow object \rightarrow c\rightarrow a\rightarrow object,继承顺序是d \rightarrow b \rightarrow c\rightarrow a。可通过__mro__方法查看

    >> d.__mro__
    (__main__.d, __main__.b, __main__.c, __main__.a, object)
    

    该遍历顺序相当于从DFS结果的尾部开始查看并记录类和超类第一次出现的顺序,如果有重复则跳过,得到一个序列,再将该序列反转即可得到MRO。

    调用d类的show方法,查看返回结果

    >> d().show()
    c show
    

    之所以返回c show是因为根据MRO的调用顺序,d在MRO序列中第一个有show方法定义的位置,即c类,停止,并调用该类中的show方法。而b中虽然继承了show方法但没有显式定义,跳过

    merge/C3算法

    面对更加复杂的调用顺序,Python 2.2起的新式类引入了merge/C3搜索算法计算MRO。考虑下面这个情况

    class d:
        pass
    class e:
        pass
    class f:
        pass
    class b(d, e):
        pass
    class c(d, f):
        pass
    class a(b, c):
        pass
    

    b\rightarrow d \rightarrow ec\rightarrow d \rightarrow f,根据merge/C3算法决定MRO。

    定义:

    • L[C]表示C的线性继承关系,如L[object] = objectL[d] = do,这里的do表示从do object
    • C_1C_2...C_N表示类C_1C_N的序列,该序列头部元素(head)=C_1,尾部定义为(tail)=C_2...C_N
    • C继承的基类自左向右分别表示为B_1B_2...B_N

    merge算法过程如下
    L[C(B_1...B_N)] = C + merge(L[B_1]...L[B_N], B_1, ..., B_N)
    其中merge方法的计算规则如下:在L[B_1]...L[B_N], B_1, ...,B_N中,取L[B_1]的head,如果该元素不在L[B_2]...L[B_N], B_1, ...,B_N的尾部序列中,将该元素从所有列表中删除,否则去L[B_2]的head继续相同的判断。直到列表为空(结束),或没有找到任何符合要求的头元素(引发异常)。

    用merge算法计算上面继承结构的MRO
    L[o]=o, L(d)=do, L(e)=eo, L(f)=fo

    \begin{aligned} L[b] &=b+merge(L[d], L[e]) \\ & = b+merge(do, eo) \\ & = b + d + merge(o, eo)\\ & = b + d + e + merge(o, o)\\ & = b+d+e+o\\ & = bdeo \end{aligned}
    \begin{aligned} L[c] &=c+merge(L[d], L[f]) \\ & = c+merge(do, fo) \\ & = c + d + merge(o, fo)\\ & = c + d + f + merge(o, o)\\ & = c+d+f+o\\ & = cdfo \end{aligned}
    \begin{aligned} L[a] &=a+merge(L[b], L[c]) \\ & = a+merge(bdeo, cdfo) \\ & = a + b + merge(deo, cdfo)\\ & = a + b + c + merge(deo, dfo)\\ & = a+b+c+d+merge(eo, fo)\\ & = a+b+c+d+e+merge(o, fo)\\ & = a+b+c+d+e+f+merge(o, o)\\ & = a+b+c+d+e+f+o\\ & = abcdefo \end{aligned}
    查看__mro__方法

    >> a.__mro__
    (__main__.a,
     __main__.b,
     __main__.c,
     __main__.d,
     __main__.e,
     __main__.f,
     object)
    

    验证了merge算法的结果。

    如果在merge算法执行时出现merge(ab, ba)这种情况,解释器无法知道如何处理这种情况,直接抛出异常。

    在类的多继承中,避免出现菱形调用的复杂情况。

    Reference

    1 编写高质量代码 改善Python程序的91个建议,张颖等著,机械工业出版社

    相关文章

      网友评论

          本文标题:Python类-多继承和MRO, since 2022-05-1

          本文链接:https://www.haomeiwen.com/subject/oezourtx.html