1. 程式人生 > >python多繼承 python經典類的MRO python新式類的MRO C3演算法 super

python多繼承 python經典類的MRO python新式類的MRO C3演算法 super

python多繼承

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

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

 

  多繼承用起來簡單. 也很好理解. 但是多繼承中, 存在著這樣一個問題. 當兩個父類中出
現了重名方法的時候. 這時該怎麼辦呢? 這時就涉及到如何查詢父類方法的這麼一個問題.


即MRO(method resolution order) 問題. 在python中這是一個很複雜的問題. 因為在不同的
python版本中使⽤的是不同的演算法來完成MRO的. 首先. 我們目前能見到的有兩個版本:

python2

在python2中存在兩種類.

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

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

python3

python3中使⽤用的都是新式類. 如果基類誰都不繼承. 那這個類會預設繼承object

 

經典類的MRO
  雖然在python3中已經不存在經典類了. 但是經典類的MRO最好還是學一學. 這是一種
樹形結構遍歷的一個最直接的案例例. 在python的繼承體系中. 我們可以把類與類繼承關係化
成⼀個樹形結構的圖. 來, 上程式碼:

 

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

 

圖中每個圈都是準備要送雞蛋的住址. 箭頭和⿊線表⽰線路. 那送雞蛋的順序告訴你入
⼝在最下面R. 並且必須從左往右送. 那怎麼送呢?

 

  如圖. 肯定是按照123456這樣的順序來送. 那這樣的順序就叫深度優先遍歷. 而如果是
142356呢? 這種被稱為廣度優先遍歷. 好了. 深度優先就說這麼多. 那麼上面那個圖怎麼找的
呢? MRO是什麼呢? 很簡單. 記住. 從頭開始. 從左往右. 一條路跑到頭, 然後回頭. 繼續一條
路跑到頭. 就是經典類的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 G(E):
    pass
class H(G, F):
    pass

首先. 我們要確定從H開始找. 也就是說. 建立的是H的物件.
如果從H找. 那找到G+F的父類的C3, 我們設C3演算法是L(x) , 即給出x類. 找到x的MRO
  L(H) = H + L(G) + L(F) + GF
繼續從程式碼中找G和F的⽗類往裡面帶
  L(G) = G + L(E) + E
  L(F) = F + L(D)+ L(E) + DE
繼續找E 和 D
  L(E) = E + L(C) + L(A) + CA
  L(D) = D + L(B) + L(C) + BC
繼續找B和C
  L(B) = B + L(A) + A
  L(C) = C + L(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, C, A
繼續帶.
  L(G) = (G,) + (E, C, A) + (E) -> G, E, C, A
  L(F) = (F,) + (D, B, C, 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__.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. 當子類方法想呼叫父類(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().__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順序中下一個類的方法
        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是誰.

#Add2
#Mult
#哈哈
#init
#5.0
#8.0

 

#Add2
#init
#2
#4

 

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