super() 的入門使用
在類的繼承中疆股,如果重定義某個(gè)方法,該方法會覆蓋父類的同名方法倒槐,但有時(shí)旬痹,我們希望能同時(shí)實(shí)現(xiàn)父類的功能,這時(shí)讨越,我們就需要調(diào)用父類的方法了两残,可通過使用 super 來實(shí)現(xiàn),比如:
class Animal(object):
def __init__(self, name):
self.name = name
def greet(self):
print 'Hello, I am %s.' % self.name
class Dog(Animal):
def greet(self):
super(Dog, self).greet() # Python3 可使用 super().greet()
print 'WangWang...'
在上面把跨,Animal 是父類人弓,Dog 是子類,我們在 Dog 類重定義了 greet 方法着逐,為了能同時(shí)實(shí)現(xiàn)父類的功能崔赌,我們又調(diào)用了父類的方法,看下面的使用:
>>> dog = Dog('dog')
>>> dog.greet()
Hello, I am dog.
WangWang..
super 的一個(gè)最常見用法可以說是在子類中調(diào)用父類的初始化方法了耸别,比如:
class Base(object):
def __init__(self, a, b): # 創(chuàng)建實(shí)例時(shí)健芭,父類中有2個(gè)必須輸入的參數(shù)a,b
self.a = a
self.b = b
class A(Base):
def __init__(self, a, b, c): # 繼承時(shí),init后需要傳入的參數(shù)要大于下面init后的傳參數(shù)
super(A, self).__init__(a, b)
self.c = c
# 錯(cuò)誤示范
class A(Base):
def __init__(self,a,c):
super(A,self).__init__(a,b) # 這里會報(bào)錯(cuò)秀姐,因?yàn)樯戏轿刺岬絙慈迈,不能寫入b
self.c = c
-----------------------------------------------
# 要寫入b,可以把 b 用值表示省有,而不是變量
In [115]: class Base(object):
...: def __init__(self,a,b):
...: self.a = a
...: self.b = b
...:
In [116]: class AA(Base):
...: def __init__(self,a,c):
...: super(AA,self).__init__(a,100)
...: self.c = c
...:
In [118]: test = AA(1,2)
In [119]: test.a
Out[119]: 1
In [120]: test.b # 這里,b 就被默認(rèn)賦值100了痒留,就不會報(bào)錯(cuò)了
Out[120]: 100
In [121]: test.c
Out[121]: 2
深入 super( )
看了上面的使用谴麦,你可能會覺得 super 的使用很簡單,無非就是獲取了父類狭瞎,并調(diào)用父類的方法细移。其實(shí),在上面的情況下熊锭,super 獲得的類剛好是父類弧轧,但在其他情況就不一定了,super 其實(shí)和父類沒有實(shí)質(zhì)性的關(guān)聯(lián)碗殷。
讓我們看一個(gè)稍微復(fù)雜的例子精绎,涉及到多重繼承,代碼如下:
class Base(object):
def __init__(self):
print "enter Base"
print "leave Base"
class A(Base):
def __init__(self):
print "enter A"
super(A, self).__init__()
print "leave A"
class B(Base):
def __init__(self):
print "enter B"
super(B, self).__init__()
print "leave B"
class C(A, B):
def __init__(self):
print "enter C"
super(C, self).__init__()
print "leave C"
其中锌妻,Base 是父類代乃,A, B 繼承自 Base, C 繼承自 A, B,它們的繼承關(guān)系如下:
Base
/ \
/ \
A B
\ /
\ /
C
現(xiàn)在仿粹,讓我們看一下使用:
>>> c = C()
enter C
enter A
enter B
enter Base
leave Base
leave B
leave A
leave C
如果你認(rèn)為 super 代表『調(diào)用父類的方法』搁吓,那你很可能會疑惑為什么 enter A 的下一句不是 enter Base 而是 enter B。原因是吭历,super 和父類沒有實(shí)質(zhì)性的關(guān)聯(lián)堕仔,現(xiàn)在讓我們搞清 super 是怎么運(yùn)作的。
MRO 列表
事實(shí)上晌区,對于你定義的每一個(gè)類摩骨,Python 會計(jì)算出一個(gè)方法解析順序(Method Resolution Order, MRO)列表,它代表了類繼承的順序朗若,我們可以使用下面的方式獲得某個(gè)類的 MRO 列表:
>>> C.mro() # or C.__mro__ or C().__class__.mro()
[__main__.C, __main__.A, __main__.B, __main__.Base, object]
那這個(gè) MRO 列表的順序是怎么定的呢恼五,它是通過一個(gè)C3 線性化算法來實(shí)現(xiàn)的,這里我們就不去深究這個(gè)算法了哭懈,感興趣的讀者可以自己去了解一下灾馒,總的來說,一個(gè)類的 MRO 列表就是合并所有父類的 MRO 列表遣总,并遵循以下三條原則:
1 . 子類永遠(yuǎn)在父類前面
2 . 如果有多個(gè)父類睬罗,會根據(jù)它們在列表中的順序被檢查
3 . 如果對下一個(gè)類存在兩個(gè)合法的選擇,選擇第一個(gè)父類
super 原理
def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls) + 1]
其中彤避,cls 代表類傅物,inst 代表實(shí)例,上面的代碼做了兩件事:
獲取 inst 的 MRO 列表
查找 cls 在當(dāng)前 MRO 列表中的 index, 并返回它的下一個(gè)類琉预,即 mro[index + 1]
當(dāng)你使用 super(cls, inst)
時(shí)董饰,Python 會在 inst
的 MRO 列表上搜索 cls
的下一個(gè)類。
現(xiàn)在,讓我們回到前面的例子卒暂。
首先看類 C 的__init__
方法:
super(C, self).__init__()
這里的 self 是當(dāng)前 C 的實(shí)例啄栓,self.__class__.mro()
結(jié)果是:
[__main__.C, __main__.A, __main__.B, __main__.Base, object]
可以看到,C 的下一個(gè)類是 A也祠,于是昙楚,跳到了 A 的__init__
,這時(shí)會打印出 enter A诈嘿,并執(zhí)行下面一行代碼:
super(A, self).__init__()
注意堪旧,這里的 self 也是當(dāng)前 C 的實(shí)例,MRO 列表跟上面是一樣的奖亚,搜索 A 在 MRO 中的下一個(gè)類淳梦,發(fā)現(xiàn)是 B,于是昔字,跳到了 B 的__init__
爆袍,這時(shí)會打印出 enter B,而不是 enter Base作郭。
整個(gè)過程還是比較清晰的陨囊,關(guān)鍵是要理解 super
的工作方式,而不是想當(dāng)然地認(rèn)為 super
調(diào)用了父類的方法夹攒。
小結(jié)
事實(shí)上蜘醋,super
和父類沒有實(shí)質(zhì)性的關(guān)聯(lián)。
super(cls, inst)
獲得的是 cls
在inst
的 MRO 列表中的下一個(gè)類芹助。