MRO和C3算法


本節主要內容:

1.python多繼承

2.python經典類的MRO

3.python新式類的MRO、C3算法

4.super是什么鬼?

一、python多繼承

  在前⾯的學習過程中. 我們已經知道了Python中類與類之間可以有繼承關系. 當出現了x是
  ⼀種y的的時候. 就可以使⽤繼承關系. 即"is-a" 關系. 在繼承關系中. ⼦類⾃動擁有⽗類中除
  了私有屬性外的其他所有內容. python⽀持多繼承. ⼀個類可以擁有多個⽗類.

class ShenXian: # 神仙
     def fei(self):
         print("神仙都會⻜")
class Monkey: # 猴
     def chitao(self):
         print("猴⼦喜歡吃桃⼦")
class SunWukong(ShenXian, Monkey): # 孫悟空是神仙, 同時也是⼀只猴
     pass
sxz = SunWukong() # 孫悟空
sxz.chitao() # 會吃桃⼦
sxz.fei() # 會⻜
 

  此時,孫悟空是一只猴子,同時也是一個神仙。那孫悟空繼承了這兩個類。孫悟空自然就可以執行這兩個類中的方法。

  多繼承中,存在這樣一個問題。當兩個父類中出現了重名方法的時候。這時怎么辦?這就涉及到如何查找父類方法的這么一個問題即MRO(method resoluthion order)問題。在python中這是一個很復雜的問題。因為在不同的python版本中使用的是不同的算法來完成MRO的。首先,我們目前見到的有兩個版本:

1.python 2

  在python2中存在兩種類

  一個叫經典類。在python2.2之前。一直使用的是經典類。經典類在基類的根如果什么都不寫。表示繼承xxx

  一個叫新式類。在python2.2之后出現了新式類。新式類的特點是基類的根是object

2.python 3

  在python 3種使用的都是新式類。如果基類誰都不繼承,那這個類會默認繼承object

二、經典類的MRO

  雖然在python 3中已經不存在經典類了。但是經典類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。很簡單。畫圖即可:

  

  繼承關系圖已經有了。那如何進行查找呢?記住一個原則。在經典類中采用的是深度優先遍歷方案。什么是深度優先。就是一條路走到頭。然后再回來。繼續找下一個。比如。有一個快遞員。去給每家每戶送快遞。

  圖中每個圈都是准備要送雞蛋的住址。箭頭和黑線表示線路。送快遞的順序告訴你入口再最下面R,並且必須從左往右送。那怎么送呢?

  如圖。肯定是按照123456這樣的順序來送。那這樣的順序就叫深度優先遍歷。而如果是1423456呢?這種被成為廣度優先遍歷。MRO是什么呢?很簡單從頭開始。從左往右。一條路跑到頭,然后回頭。繼續一條路跑到頭。就是經典類的MRO算法。

  類的MRO: Foo-> H -> G -> F -> E -> D -> B -> A -> C. 你猜對了么?

三、新式類的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 G(E):
     pass
class H(G, F):
     pass

  首先。我們要確定從H開始找。也就是說。創建的是H的對象。

  如果從H找。那找到H+H的父類的C3,我們設C3算法是L(X),即給出X類,找到X的MRO

  L(H) = H + L(G) + L(F)
  繼續從代碼中找G和F的⽗類往⾥⾯帶
  L(G) = G + L(E)
  L(F) = F + L(D)+ L(E)
  繼續找E 和 D
  L(E) = E + L(C) + L(A)
  L(D) = D + L(B) + L(C)
  繼續找B和C
  L(B) = B + L(A)
  L(C) = C + L(A)

  

  

  最后就剩下⼀個A了. 也就不⽤再找了. 接下來. 把L(A) 往⾥帶. 再推回去. 但要記住. 這⾥的
+ 表⽰的是merge. merge的原則是⽤每個元組的頭⼀項和后⾯元組的除頭⼀項外的其他元

素進⾏比較, 看是否存在. 如果存在. 就從下⼀個元組的頭⼀項繼續找. 如果找不到. 就拿出來.
作為merge的結果的⼀項. 以此類推. 直到元組之間的元素都相同. 也就不⽤再找了.

  L(B) =(B,) + (A,) -> (B, A)
  L(C) =(C,) + (A,) -> (C, A)
繼續帶.
  L(E) = (E,) + (C, A) + (A) -> E, C, A
  L(D) = (D,) + (B, A) + (C, A) -> D, B, A
繼續帶.
  L(G) = (G,) + (E, C, A) -> G, E, C, A
  L(F) = (F,) + (D, B, A) + (E, C, A) -> F, D, B, E, C, A
加油,最后了

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

算完了. 最終結果 HGFDBECA. 那這個算完了. 如何驗證呢? 其實python早就給你准備好
了. 我們可以使⽤類名.__mro__獲取到類的MRO信息.

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

  結果OK. 那既然python提供了. 為什么我們還要如此⿇煩的計算MRO呢? 因為筆
試.......你在筆試的時候, 是沒有電腦的. 所以這個算法要知道. 並且簡單的計算要會. 真是項⽬
開發的時候很少有⼈這么去寫代碼.

  這個說完了. 那C3到底怎么看更容易呢? 其實很簡單. C3是把我們多個類產⽣的共同繼
承留到最后去找. 所以. 我們也可以從圖上來看到相關的規律. 這個要⼤家⾃⼰多寫多畫圖就
能感覺到了. 但是如果沒有所謂的共同繼承關系. 那⼏乎就當成是深度遍歷就可以了.

四、super是什么鬼?

  super()可以幫我們執⾏MRO中下⼀個⽗類的⽅法. 通常super()有兩個使⽤的地⽅: 

  1.可以訪問父類的構造方法

  2.當子類方法想調用父類(FRO)中的方法

  第一種:

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().__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 Foo:
     def func1(self):
         super().func1() # 此時找的是MRO順序中下⼀個類的func1()⽅法
         print("我的⽼家. 就住在這個屯")

class Bar:
     def func1(self):
         print("你的⽼家. 不在這個屯")

class Ku(Foo, Bar):
     def func1(self):
         super().func1() # 此時super找的是Foo
         print("他的⽼家. 不知道在哪個屯")

k = Ku() # 先看MRO . KU, FOO, BAR object
k.func1()

k2 = Foo() # 此時的MRO. Foo object
k2.func1() # 報錯

  

最后,這是一個面試題

MRO+super面試題

class Init(object):
     def __init__(self, v):
         print("init")
         self.val = v

class Add2(Init):
     def __init__(self, val):
         print("Add2")
         super(Add2, self).__init__(val)
         print(self.val)
         self.val += 2
class Mult(Init):
     def __init__(self, val):
         print("Mult")
         super(Mult, self).__init__(val)
         self.val *= 5
class HaHa(Init):
     def __init__(self, val):
         print("哈哈")
         super(HaHa, self).__init__(val)
         self.val /= 5
class Pro(Add2,Mult,HaHa): #
     pass
class Incr(Pro):
     def __init__(self, val):
         super(Incr, self).__init__(val)
        self.val+= 1
# Incr Pro Add2 Mult HaHa Init
p = Incr(5)
print(p.val)
c = Add2(2)
print(c.val)

  提示,先算MRO。然后看清楚self是誰。

結論:不管super()寫在哪。在哪兒執行。一定先找到MRO列表。根據MRO列表的順序往下找。否則一切都是錯的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM