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