python面試的100題(15)

41.super函數的具體用法和場景

爲了調用父類(超類)的一個方法,可使用 super() 函數,好比:html

class A:
    def spam(self):
        print('A.spam')

class B(A):
    def spam(self):
        print('B.spam')
        super().spam()  # Call parent spam()

super() 函數的一個常見用法是在 __init__() 方法中確保父類被正確的初始化了:python

class A:
    def __init__(self):
        self.x = 0

class B(A):
    def __init__(self):
        super().__init__()
        self.y = 1

super() 的另一個常見用法出如今覆蓋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__() 的實現包含一個名字檢查。 若是某個屬性名如下劃線(_)開頭,就經過 super() 調用原始的 __setattr__() , 不然的話就委派給內部的代理對象 self._obj 去處理。 這看上去有點意思,由於就算沒有顯式的指明某個類的父類, super() 仍然能夠有效的工做。ide

討論

實際上,你們對於在Python中如何正確使用 super() 函數廣泛知之甚少。 你有時候會看到像下面這樣直接調用父類的一個方法:wordpress

class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        Base.__init__(self)
        print('A.__init__')

儘管對於大部分代碼而言這麼作沒什麼問題,可是在更復雜的涉及到多繼承的代碼中就有可能致使很奇怪的問題發生。 好比,考慮以下的狀況:函數

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__')

若是你運行這段代碼就會發現 Base.__init__() 被調用兩次,以下所示:spa

>>> c = C()
Base.__init__
A.__init__
Base.__init__
B.__init__
C.__init__
>>>

可能兩次調用 Base.__init__() 沒什麼壞處,但有時候卻不是。 另外一方面,假設你在代碼中換成使用 super() ,結果就很完美了:代理

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__')

運行這個新版本後,你會發現每一個 __init__() 方法只會被調用一次了:code

>>> c = C()
Base.__init__
B.__init__
A.__init__
C.__init__
>>>

爲了弄清它的原理,咱們須要花點時間解釋下Python是如何實現繼承的。 對於你定義的每個類,Python會計算出一個所謂的方法解析順序(MRO)列表。 這個MRO列表就是一個簡單的全部基類的線性順序表。例如:htm

>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class '__main__.Base'>, <class 'object'>)
>>>

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

而這個MRO列表的構造是經過一個C3線性化算法來實現的。 咱們不去深究這個算法的數學原理,它實際上就是合併全部父類的MRO列表並遵循以下三條準則:

  • 子類會先於父類被檢查
  • 多個父類會根據它們在列表中的順序被檢查
  • 若是對下一個類存在兩個合法的選擇,選擇第一個父類

老實說,你所要知道的就是MRO列表中的類順序會讓你定義的任意類層級關係變得有意義。

當你使用 super() 函數時,Python會在MRO列表上繼續搜索下一個類。 只要每一個重定義的方法統一使用 super() 並只調用它一次, 那麼控制流最終會遍歷完整個MRO列表,每一個方法也只會被調用一次。 這也是爲何在第二個例子中你不會調用兩次 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'
>>>

可是,若是你使用多繼承的話看看會發生什麼:

>>> class B:
...     def spam(self):
...         print('B.spam')
...
>>> class C(A,B):
...     pass
...
>>> c = C()
>>> c.spam()
A.spam
B.spam
>>>

你能夠看到在類A中使用 super().spam() 實際上調用的是跟類A毫無關係的類B中的 spam() 方法。 這個用類C的MRO列表就能夠徹底解釋清楚了:

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

在定義混入類的時候這樣使用 super() 是很廣泛的。能夠參考8.13和8.18小節。

然而,因爲 super() 可能會調用不是你想要的方法,你應該遵循一些通用原則。 首先,確保在繼承體系中全部相同名字的方法擁有可兼容的參數簽名(好比相同的參數個數和參數名稱)。 這樣能夠確保 super() 調用一個非直接父類方法時不會出錯。 其次,最好確保最頂層的類提供了這個方法的實現,這樣的話在MRO上面的查找鏈確定能夠找到某個肯定的方法。

在Python社區中對於 super() 的使用有時候會引來一些爭議。 儘管如此,若是一切順利的話,你應該在你最新代碼中使用它。 Raymond Hettinger爲此寫了一篇很是好的文章 「Python’s super() Considered Super!」 , 經過大量的例子向咱們解釋了爲何 super() 是極好的。

參考地址:https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p07_calling_method_on_parent_class.html
相關文章
相關標籤/搜索