super函數沒有那麼簡單-super原理剖析

開始以前,先出一道題:算法

 1 #super函數探討  
 2 class A(object):  
 3     def __init__(self):  
 4         print 'A.__init__'  
 5   
 6 class B(A):  
 7     def __init__(self):  
 8         super(B, self).__init__()  
 9         print 'B.__init__'  
10   
11 class C(A):  
12     def __init__(self):  
13         super(C, self).__init__()  
14         print 'C.__init__'  
15   
16 class D(B, C):  
17     def __init__(self):  
18         super(D, self).__init__()  
19         print 'D.__init__'  
20   
21 d = D()  
View Code

上面的運行結果是什麼?ide

是下面的結果嗎?函數

A.__init__
B.__init__
D.__init__

正確答案:spa

A.__init__
C.__init__
B.__init__
D.__init__

有沒有疑惑?super()函數不是調用指定類的父類的方法嗎!打印了A.__init__下一句爲何是C.__init__呢?code

根本緣由是:blog

super 和父類沒有實質性的關聯繼承

首先,咱們知道新式類採用廣度優先算法,咱們來看一下上面的繼承關係:it

那麼,Python是如何實現繼承的,繼承順序又是由誰決定的呢? 對於你定義的每個類而已,Python會計算出一個所謂的方法解析順序(MRO Method Resolution Order)列表。類的繼承順序就是由這個MRO決定的io

MRO經過class.__mro__來查看,咱們來打印一下上面例子中的MRO:event

print D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

注意__mro__是類的屬性,實例沒有該屬性

這個MRO列表就是一個簡單的全部基類的線性順序表。爲了實現繼承,Python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類爲止。

而這個MRO列表的構造是經過一個C3線性化算法來實現的。 它實際上就是合併全部父類的MRO列表並遵循以下三條準則:

1)子類會先於父類被檢查

2)多個父類會根據它們在列表中的順序被檢查

3)若是對下一個類存在兩個合法的選擇,選擇第一個父類

好像仍是沒明白爲何例子中,打印了A.__init__下一句爲何是C.__init__呢?

咱們使用一個函數來解釋一下super的原理:

def super(cls, inst):
    mro = inst.__class__.mro()
    return mro[mro.index(cls) + 1]

其中,cls 表明類,inst 表明實例,上面的代碼作了兩件事:

1)獲取 inst 的 MRO 列表

2)查找 cls 在當前 MRO 列表中的 index, 並返回它的下一個類,即 mro[index + 1]

當你使用 super(cls, inst) 時,Python 會在 inst 的 MRO 列表上搜索 cls 的下一個類。

是否是有一種豁然開朗的趕腳!讓咱們回到例子中,這裏我畫出了整個流程;

從上面的流程圖就能夠看出打印的順序是對的!

瞭解了super的原理,那麼也就能夠理解下面這段有趣的代碼了:

1)執行下面代碼

1 class A(object):
2     def go(self):
3         print 'A go'
4         super(A, self).go()
5 
6 a = A()
7 a.go()
View Code

會報錯:

AttributeError: 'super' object has no attribute 'go'

2)執行下面代碼:

 1 class A(object):
 2     def go(self):
 3         print 'A go'
 4         super(A, self).go()
 5 
 6 class B(object):
 7     def go(self):
 8         print 'B go'
 9 
10 class C(A, B):
11     pass
12 
13 c = C()
14 c.go()
View Code

不會報錯,結果爲:

A go
B go

充分說明了super 和父類沒有實質性的關聯

另外,咱們想出了super之外,還有一種直接調用父類方法的方法,以下:

 1 #super函數探討
 2 class A(object):
 3     def __init__(self):
 4         print 'A.__init__'
 5 
 6 class B(A):
 7     def __init__(self):
 8         # super(B, self).__init__()
 9         A.__init__(self)
10         print 'B.__init__'
11 
12 class C(A):
13     def __init__(self):
14         # super(C, self).__init__()
15         A.__init__(self)
16         print 'C.__init__'
17 
18 class D(B, C):
19     def __init__(self):
20         # super(D, self).__init__()
21         B.__init__(self)
22         C.__init__(self)
23         print 'D.__init__'
24 
25 d = D()
View Code

爲何不用這種方法呢?咱們運行一下,看一下,結果爲:

A.__init__
B.__init__
A.__init__
C.__init__
D.__init__

很明顯,A的構造函數運行了兩次,這不是咱們所但願的;因此仍是用super吧!

相關文章
相關標籤/搜索