函數super()用於子類調用父類(超類)的方法。
你肯定要問為什麼在子類中在要用函數super()去調用父類中的方法呢?
我像下面這樣用不行麼?
class A:
def fun1(self):
print('Enter A')
class B(A):
def fun1(self):
A.fun1(self)
print('Enter B')
object1 = B()
object1.fun1()
運行結果如下:
是,的確是可以像上面這樣,但是這樣做的缺點是,當一個子類的父類發生變化時(如類B的父類由A變為C時),必須遍歷整個B類的定義,把所有的相關調用名全修改過來。
上面是例子很簡單,只有一處需要修改,上面的缺點並不足以成為問題,但是當代碼量龐大時,這樣的修改可能是災難性的。
Python中向我們提供了內置函數super()來克服這個缺點和問題,來看一個使用內置函數super()來調用父類方法的示例。
class A:
def fun1(self):
print('Enter A')
class B(A):
def fun1(self):
super(B, self).fun1()
print('Enter B')
object1 = B()
object1.fun1()
運行結果如下:
通過上面的代碼,我們可以看出,如果類B的父類A改為了C,則只需要把類定義中的第一句語句:
由
class B(A):
改為
class B(C):
就行了,而不用更改相關的調用語句。
在Python3中,語句
super(B, self).fun1()
可以簡寫為:
super().fun1()
從上面來看,函數super()雖然把父類的名字隱去了,使得調用父類的方法與父類的名字無關,但是也帶來了問題。什麼問題呢?看下面的例子:
class A:
def fun1(self):
print('Enter A')
class B(A):
def fun1(self):
A.fun1(self)
print('Enter B')
class C(A):
def fun1(self):
A.fun1(self)
print('Enter C')
class D(B, C):
def fun1(self):
B.fun1(self)
print('Enter D')
object1 = D()
object1.fun1()
通過上面的代碼,我們可以很清晰地看出子類D有兩個父類B和C,即D是一種多重繼承。在D中調用的方法fun1()是父類B中的方法fun1(),所以運行結果如下:
但我們若用函數super隱去父類名,這就出問題了,代碼如下:
class A:
def fun1(self):
print('Enter A')
class B(A):
def fun1(self):
super().fun1()
print('Enter B')
class C(A):
def fun1(self):
super().fun1()
print('Enter C')
class D(B, C):
def fun1(self):
super().fun1()
print('Enter D')
object1 = D()
object1.fun1()
上面的代碼存在的問題就是類D對父類中fun1的調用不知道該去調用哪個父類中的fun1。函數super()是這樣處理這個問題的:采用MRO順序把所有解析到的父類fun1都運行一遍且每個找到的方法fun1只運行一次。
我們可以用下面這條語句把類D的MRO順序打印出來:
print(D.__mro__)
示例代碼如下:
class A:
def fun1(self):
print('Enter A')
class B(A):
def fun1(self):
super().fun1()
print('Enter B')
class C(A):
def fun1(self):
super().fun1()
print('Enter C')
class D(B, C):
def fun1(self):
super().fun1()
print('Enter D')
print(D.__mro__) # 把類D的MRO順序打印出來
運行結果如下:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
根據上面的MRO順序,我們可以知道類D查詢某方法的順序為類D→類B→類C→類A。
根據以上順序,我們有以下關於object1.fun1()這條語句的執行過程:
object1.fun1()的第一條需要執行的語句為類D中的語句super().fun1(),這條語句在執行後:
首先在類D中找尋方法fun1(),找到了,找到之後將類D中的方法fun1()編號為①
然後在類B中找尋方法fun1(),找到了,找到之後將類B中的方法fun1()編號為②
然後在類C中找尋方法fun1(),找到了,找到之後將類C中的方法fun1()編號為③
然後在類A中找尋方法fun1(),找到了,找到之後將類A中的方法fun1()編號為④
然後按照④→③→②→①的順序依次執行一遍一個四個fun1()
當執行④時,函數體唯一的一條語句“print(‘Enter A’)”打印出字符串Enter A
④執行完後執行③,當執行③時,第一條語句為super().fun1(),於是按下面的順序去搜索fun1:
首先在類C中找尋方法fun1(),找到了,找到之後發現類C中的方法fun1()已經被編號為③
然後在類A中找尋方法fun1(),找到了,找到之後發現類A中的方法fun1()已經被編號為④
然後按照④→③的順序依次執行一遍一個兩個fun1(),正准備執行④的時候,發現④已經被執行過一次了,所以不再執行④,於是直接執行③,執行③的時候發現其第一條語句super().fun1()已經被執行過一次了,所以不再執行這條語句,於是執行語句print(‘Enter C’),打印出字符串Enter C,至此③執行完畢。
③執行完後執行②,當執行②時,第一條語句為super().fun1(),於是按下面的順序去搜索fun1:
首先在類B中找尋方法fun1(),找到了,找到之後發現類B中的方法fun1()已經被編號為②
然後在類A中找尋方法fun1(),找到了,找到之後發現類A中的方法fun1()已經被編號為④
然後按照④→②的順序依次執行一遍一個兩個fun1(),正准備執行④的時候,發現④已經被執行過一次了,所以不再執行④,於是直接執行②,執行②的時候發現其第一條語句super().fun1()已經被執行過一次了,所以不再執行這條語句,於是執行語句print(‘Enter B’),打印出字符串Enter B。
②執行完後執行①,當執行①時,第一條語句為super().fun1(),這條語句已經被執行過了,所以不再執行這條語句,於是執行剩下的一條語句print(‘Enter D’),打印出字符串Enter D。
綜上,輸出信息應該如下:
Enter A
Enter C
Enter B
Enter D
我們看下程序的運行結果是不是這樣的:
從上面的截圖來看,程序實際運行的結果和我們的分析結果是一致的,所以我們的分析是正確的。