標籤:

Python中既然可以直接通過父類名調用父類方法為什麼還會存在super函數?

比如

class Child(Parent):
def __init__(self):
Parent.__init__(self)

這種方式與super(Child, self).__init__()有區別么?


針對你的問題,答案是可以,並沒有區別。但是這題下的回答我感覺都不夠好。

要談論 super,首先我們應該無視 "super" 這個名字帶給我們的干擾。

不要一說到 super 就想到父類!super 指的是 MRO 中的下一個類!

不要一說到 super 就想到父類!super 指的是 MRO 中的下一個類!

不要一說到 super 就想到父類!super 指的是 MRO 中的下一個類!

一說到 super 就想到父類這是初學者很容易犯的一個錯誤,也是我當年犯的錯誤。

忘記了這件事之後,再去看這篇文章:Python』s super() considered super!

這是 Raymond Hettinger 寫的一篇文章,也是全世界公認的對 super 講解最透徹的一篇文章,凡是討論 super 都一定會提到它(當然還有一篇 Pythons Super Considered Harmful)。

如果不想看長篇大論就去看這個答案,super 其實幹的是這件事:

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

兩個參數 cls 和 inst 分別做了兩件事:

1. inst 負責生成 MRO 的 list

2. 通過 cls 定位當前 MRO 中的 index, 並返回 mro[index + 1]

這兩件事才是 super 的實質,一定要記住!

MRO 全稱 Method Resolution Order,它代表了類繼承的順序。後面詳細說。

舉個例子

class Root(object):
def __init__(self):
print("this is Root")

class B(Root):
def __init__(self):
print("enter B")
# print(self) # this will print &<__main__.D object at 0x...&>
super(B, self).__init__()
print("leave B")

class C(Root):
def __init__(self):
print("enter C")
super(C, self).__init__()
print("leave C")

class D(B, C):
pass

d = D()
print(d.__class__.__mro__)

輸出

enter B
enter C
this is Root
leave C
leave B
(&, &, &, &, &)

知道了 super 和父類其實沒有實質關聯之後,我們就不難理解為什麼 enter B 下一句是 enter C 而不是 this is Root(如果認為 super 代表「調用父類的方法」,會想當然的認為下一句應該是this is Root)。流程如下,在 B 的 __init__ 函數中:

super(B, self).__init__()

首先,我們獲取 self.__class__.__mro__,注意這裡的 self 是 D 的 instance 而不是 B 的

(&, &, &, &, &)

然後,通過 B 來定位 MRO 中的 index,並找到下一個。顯然 B 的下一個是 C。於是,我們調用 C 的 __init__,打出 enter C。

順便說一句為什麼 B 的 __init__ 會被調用:因為 D 沒有定義 __init__,所以會在 MRO 中找下一個類,去查看它有沒有定義 __init__,也就是去調用 B 的 __init__。

其實這一切邏輯還是很清晰的,關鍵是理解 super 到底做了什麼。

於是,MRO 中類的順序到底是怎麼排的呢?Python』s super() considered super!中已經有很好的解釋,我翻譯一下:

在 MRO 中,基類永遠出現在派生類後面,如果有多個基類,基類的相對順序保持不變。

最後的最後,提醒大家.

什麼 super 啊,MRO 啊,都是針對 new-style class。如果不是 new-style class,就老老實實用父類的類名去調用函數吧。

--

更新,回答 @裘堪將的問題。

class A(object) :
def __init__(self, strName, *args) :
self.name = strName
super(A, self).__init__(*args)

class B(object) :
def __init__(self, iID) :
self.ID= iID

class C(A, B):
def __init__(self, *args)
super(C, self).__init__(*args)

沒有考慮 keyword 參數,如果要要考慮,也是類似的,加後面就行了


super 是用來解決多重繼承問題的,直接用類名調用父類方法在使用單繼承的時候沒問題,但是如果使用多繼承,會涉及到查找順序(MRO)、重複調用(鑽石繼承)等種種問題。

總之前人留下的經驗就是:保持一致性。要不全部用類名調用父類,要不就全部用 super,不要一半一半。

如果沒有複雜的繼承結構,super 作用不大。而複雜的繼承結構本身就是不良設計。對於多重繼承的用法,現在比較推崇 Mixin 的方式,也就是

  • 普通類多重繼承只能有一個普通父類和若干個 Mixin 類(保持主幹單一)
  • Mixin 類不能繼承普通類(避免鑽石繼承)
  • Mixin 類應該單一職責(參考 Java 的 interface 設計,Mixin 和此極其相似,只不過附帶實現而已)

如果按照上述標準,只使用 Mixin 形式的多繼承,那麼不會有鑽石繼承帶來的重複方法調用,也不會有複雜的查找順序 —— 此時 super 是可以有無的了,用不用全看個人喜好,只是記得千萬別和類名調用的方式混用就好。


首先這兩種調用是有區別的,在多繼承出現的時候,調用是不同的,參考如下代碼:

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

class A(Base):
def __init__(self):
print(A.__init__ begin)
Base.__init__(self)
print(A.__init__ end)

class B(Base):
def __init__(self):
print(B.__init__ begin)
Base.__init__(self)
print(B.__init__ end)

class C(A,B):
def __init__(self):
print(C.__init__ begin)
A.__init__(self)
B.__init__(self)
print(C.__init__ end)

c = C()
print C.__mro__

運行結果:

C.__init__ begin
A.__init__ begin
Base.__init__
A.__init__ end
B.__init__ begin
Base.__init__
B.__init__ end
C.__init__ end
(&, &, &, &, &)

使用 super() 的多繼承代碼

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

class A(Base):
def __init__(self):
print(A.__init__ begin)
super(A, self).__init__()
print(A.__init__ end)

class B(Base):
def __init__(self):
print(B.__init__ begin)
super(B, self).__init__()
print(B.__init__ end)

class C(A,B):
def __init__(self):
print(C.__init__ begin)
super(C, self).__init__() # Only one call to super() here
print(C.__init__ end)

c = C()
print C.__mro__

運行結果:

C.__init__ begin
A.__init__ begin
B.__init__ begin
Base.__init__
B.__init__ end
A.__init__ end
C.__init__ end
(&, &, &, &, &)

當你使用 super() 函數時,Python會在MRO列表上繼續搜索下一個類。 只要每個重定義的方法統一使用 super() 並只調用它一次, 那麼控制流最終會遍歷完整個MRO列表,每個方法也只會被調用一次。 這也是為什麼在第二個例子中你不會調用兩次 Base.__init__() 的原因。

參考:8.7 調用父類方法 - python3-cookbook 2.0.0 文檔


某個時候你覺得Parent名字不好了,改成MyParent,結果不得不把每個子類都改一遍

另外給一個類獲得父類只能用super,比如:

def get_super(cls):


http://blog.csdn.net/johnsonguo/article/details/585193

這裡面寫的挺好的,可以參考下


1. python 實現了多繼承, 這個繼承的順序叫MRO(可以看看print(class.__mro__))

2. super 只是提供了一種調用父類方法. 它並不是實現多繼承的!

super 調用父類時, 其實就是搜索: class.__mro__

很多人的疑問其實是MRO 本身, 這篇文章解釋得很好: Python的方法解析順序(MRO) · M-x


英語要學好。看英語文獻很吃力。


翻譯的國外最好的寫super文章,拿走不寫

http://blog.csdn.net/qq_14898613/article/details/53792270


非常感謝,今天才搞明白,一直以為是調用父類的方法。


@laike9m 的解釋很到位了,我想說:貌似沒法解決參數傳遞問題啊,只能用python所謂的魔法,把參數全部傳遞過去,可是兩個類,作者不同,什麼事情都能發生,參數名重複呢,感覺python這個機制不怎麼樣


正好第一次接觸到這問題,搬運工

inheritance - Understanding Python super() with __init__() methods


super(A,self).func 不是把A的父類的func執行,而是把A的父類的類類型序列中的所有類型的func都執行一次。


推薦閱讀:

C 語言和 Python,該從哪個入門編程?
知乎是怎麼運行 tornado web 服務的
python教程看完了,還是不會編程?
Project Euler上的程序語言排行為什麼Matlab比Python低那麼多?
Python非同步io的Web框架-Sanic源碼分析(核心篇)

TAG:Python |