W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
你想在子類中調(diào)用父類的某個已經(jīng)被覆蓋的方法。
為了調(diào)用父類(超類)的一個方法,可以使用 super()
函數(shù),比如:
class A:
def spam(self):
print('A.spam')
class B(A):
def spam(self):
print('B.spam')
super().spam() # Call parent spam()
super()``函數(shù)的一個常見用法是在 ``__init__()
方法中確保父類被正確的初始化了:
class A:
def __init__(self):
self.x = 0
class B(A):
def __init__(self):
super().__init__()
self.y = 1
super()
的另外一個常見用法出現(xiàn)在覆蓋Python特殊方法的代碼中,比如:
class Proxy:
def __init__(self, obj):
self._obj = obj
# Delegate attribute lookup to internal obj
def __getattr__(self, name):
return getattr(self._obj, name)
# Delegate attribute assignment
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value) # Call original __setattr__
else:
setattr(self._obj, name, value)
在上面代碼中,__setattr__()
的實現(xiàn)包含一個名字檢查。如果某個屬性名以下劃線(_)開頭,就通過 super()
調(diào)用原始的 __setattr__()
,否則的話就委派給內(nèi)部的代理對象 self._obj
去處理。這看上去有點意思,因為就算沒有顯式的指明某個類的父類, super()
仍然可以有效的工作。
實際上,大家對于在Python中如何正確使用 super()
函數(shù)普遍都知之甚少。你有時候會看到像下面這樣直接調(diào)用父類的一個方法:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__')
盡管對于大部分代碼而言這么做沒什么問題,但是在更復(fù)雜的涉及到多繼承的代碼中就有可能導(dǎo)致很奇怪的問題發(fā)生。比如,考慮如下的情況:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__')
class B(Base):
def __init__(self):
Base.__init__(self)
print('B.__init__')
class C(A,B):
def __init__(self):
A.__init__(self)
B.__init__(self)
print('C.__init__')
如果你運行這段代碼就會發(fā)現(xiàn) Base.__init__()
被調(diào)用兩次,如下所示:
>>> c = C()
Base.__init__
A.__init__
Base.__init__
B.__init__
C.__init__
>>>
可能兩次調(diào)用 Base.__init__()
沒什么壞處,但有時候卻不是。另一方面,假設(shè)你在代碼中換成使用 super()
,結(jié)果就很完美了:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
super().__init__()
print('A.__init__')
class B(Base):
def __init__(self):
super().__init__()
print('B.__init__')
class C(A,B):
def __init__(self):
super().__init__() # Only one call to super() here
print('C.__init__')
運行這個新版本后,你會發(fā)現(xiàn)每個 __init__()
方法只會被調(diào)用一次了:
>>> c = C()
Base.__init__
B.__init__
A.__init__
C.__init__
>>>
為了弄清它的原理,我們需要花點時間解釋下Python是如何實現(xiàn)繼承的。對于你定義的每一個類而已,Python會計算出一個所謂的方法解析順序(MRO)列表。這個MRO列表就是一個簡單的所有基類的線性順序表。例如:
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class '__main__.Base'>, <class 'object'>)
>>>
為了實現(xiàn)繼承,Python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。
而這個MRO列表的構(gòu)造是通過一個C3線性化算法來實現(xiàn)的。我們不去深究這個算法的數(shù)學原理,它實際上就是合并所有父類的MRO列表并遵循如下三條準則:
老實說,你所要知道的就是MRO列表中的類順序會讓你定義的任意類層級關(guān)系變得有意義。
當你使用 super()
函數(shù)時,Python會在MRO列表上繼續(xù)搜索下一個類。只要每個重定義的方法統(tǒng)一使用 super()
并只調(diào)用它一次,那么控制流最終會遍歷完整個MRO列表,每個方法也只會被調(diào)用一次。這也是為什么在第二個例子中你不會調(diào)用兩次 Base.__init__()
的原因。
super()
有個令人吃驚的地方是它并不一定去查找某個類在MRO中下一個直接父類,你甚至可以在一個沒有直接父類的類中使用它。例如,考慮如下這個類:
class A:
def spam(self):
print('A.spam')
super().spam()
如果你試著直接使用這個類就會出錯:
>>> a = A()
>>> a.spam()
A.spam
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in spam
AttributeError: 'super' object has no attribute 'spam'
>>>
但是,如果你使用多繼承的話看看會發(fā)生什么:
>>> class B:
... def spam(self):
... print('B.spam')
...
>>> class C(A,B):
... pass
...
>>> c = C()
>>> c.spam()
A.spam
B.spam
>>>
你可以看到在類A中使用 super().spam()
實際上調(diào)用的是跟類A毫無關(guān)系的類B中的 spam()
方法。這個用類C的MRO列表就可以完全解釋清楚了:
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class 'object'>)
>>>
在定義混入類的時候這樣使用 super()
是很普遍的??梢詤⒖?.13和8.18小節(jié)。
然而,由于 super()
可能會調(diào)用不是你想要的方法,你應(yīng)該遵循一些通用原則。首先,確保在繼承體系中所有相同名字的方法擁有可兼容的參數(shù)簽名(比如相同的參數(shù)個數(shù)和參數(shù)名稱)。這樣可以確保 super()
調(diào)用一個非直接父類方法時不會出錯。其次,最好確保最頂層的類提供了這個方法的實現(xiàn),這樣的話在MRO上面的查找鏈肯定可以找到某個確定的方法。
在Python社區(qū)中對于 super()
的使用有時候會引來一些爭議。盡管如此,如果一切順利的話,你應(yīng)該在你最新代碼中使用它。Raymond Hettinger為此寫了一篇非常好的文章“Python’s super() Considered Super!” ,通過大量的例子向我們解釋了為什么 super()
是極好的。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: