super()方法詳解

鹹魚Chen發表於2019-06-19

單獨呼叫父類的方法

需求:編寫一個類,然後再寫一個子類進行繼承,使用子類去呼叫父類的方法1。

使用方法1列印: 胖子老闆,來包檳榔

那麼先寫一個胖子老闆的父類,執行一下:

class FatFather(object):
    def __init__(self, name):
        print('FatFather的init開始被呼叫')
        self.name = name
        print('FatFather的name是%s' % self.name)
        print('FatFather的init呼叫結束')


def main():
    ff = FatFather("胖子老闆的父親")

執行一下這個胖子老闆父類的構造方法__init__ 如下:

if __name__ == "__main__":
    main()
FatFather的init開始被呼叫
FatFather的name是胖子老闆的父親
FatFather的init呼叫結束

好了,那麼下面來寫一個子類,也就是胖子老闆類,繼承上面的類

# 胖子老闆的父類
class FatFather(object):
    def __init__(self, name):
        print('FatFather的init開始被呼叫')
        self.name = name
        print('呼叫FatFather類的name是%s' % self.name)
        print('FatFather的init呼叫結束')


# 胖子老闆類 繼承 FatFather 類
class FatBoss(FatFather):
    def __init__(self, name, hobby):
        print('胖子老闆的類被呼叫啦!')
        self.hobby = hobby
        FatFather.__init__(self, name)  # 直接呼叫父類的構造方法
        print("%s 的愛好是 %s" % (name, self.hobby))


def main():
    #ff = FatFather("胖子老闆的父親")
    fatboss = FatBoss("胖子老闆", "打鬥地主")

在這上面的程式碼中,我使用FatFather.__init__(self,name)直接呼叫父類的方法。
執行結果如下:

if __name__ == "__main__":
    main()
胖子老闆的類被呼叫啦!
FatFather的init開始被呼叫
呼叫FatFather類的name是胖子老闆
FatFather的init呼叫結束
胖子老闆 的愛好是 打鬥地主

super() 方法基本概念

除了直接使用 FatFather.__init__(self,name) 的方法,還可以使用super()方法來呼叫。

那麼首先需要看super()方法的描述和語法理解一下super() 方法的使用。

描述

super() 函式是用於呼叫父類(超類)的一個方法。

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

MRO 就是類的方法解析順序表, 其實也就是繼承父類方法時的順序表。

語法

以下是 super() 方法的語法:

super(type[, object-or-type])

引數

  • type -- 類

  • object-or-type -- 類,一般是 self

Python3.x 和 Python2.x 的一個區別是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx :

  • Python3.x 例項:
class A:
    pass
class B(A):
    def add(self, x):
        super().add(x)
  • Python2.x 例項:
class A(object):   # Python2.x 記得繼承 object
    pass
class B(A):
    def add(self, x):
        super(B, self).add(x)

單繼承使用super()

  • 使用super() 方法來改寫剛才胖子老闆繼承父類的 __init__ 構造方法
# 胖子老闆的父類
class FatFather(object):
    def __init__(self, name):
        print('FatFather的init開始被呼叫')
        self.name = name
        print('呼叫FatFather類的name是%s' % self.name)
        print('FatFather的init呼叫結束')


# 胖子老闆類 繼承 FatFather 類
class FatBoss(FatFather):
    def __init__(self, name, hobby):
        print('胖子老闆的類被呼叫啦!')
        self.hobby = hobby
        #FatFather.__init__(self,name)   # 直接呼叫父類的構造方法
        super().__init__(name)
        print("%s 的愛好是 %s" % (name, self.hobby))


def main():
    #ff = FatFather("胖子老闆的父親")
    fatboss = FatBoss("胖子老闆", "打鬥地主")

從上面使用super方法的時候,因為是單繼承,直接就可以使用了。
執行如下:

if __name__ == "__main__":
    main()
胖子老闆的類被呼叫啦!
FatFather的init開始被呼叫
呼叫FatFather類的name是胖子老闆
FatFather的init呼叫結束
胖子老闆 的愛好是 打鬥地主

那麼為什麼說單繼承直接使用就可以呢?因為super()方法如果多繼承的話,會涉及到一個MRO(繼承父類方法時的順序表) 的呼叫排序問題。

下面可以列印一下看看單繼承的MRO順序(FatBoss.__mro__)。

# 胖子老闆的父類
class FatFather(object):
    def __init__(self, name):
        print('FatFather的init開始被呼叫')
        self.name = name
        print('呼叫FatFather類的name是%s' % self.name)
        print('FatFather的init呼叫結束')


# 胖子老闆類 繼承 FatFather 類
class FatBoss(FatFather):
    def __init__(self, name, hobby):
        print('胖子老闆的類被呼叫啦!')
        self.hobby = hobby
        #FatFather.__init__(self,name)   # 直接呼叫父類的構造方法
        super().__init__(name)
        print("%s 的愛好是 %s" % (name, self.hobby))


def main():

    print("列印FatBoss類的MRO")
    print(FatBoss.__mro__)

    print()
    print("=========== 下面按照 MRO 順序執行super方法 =============")
    fatboss = FatBoss("胖子老闆", "打鬥地主")

上面的程式碼使用 FatBoss.__mro__ 可以列印出 FatBoss這個類經過 python解析器的 C3演算法計算過後的繼承呼叫順序。
執行如下:

if __name__ == "__main__":
    main()
列印FatBoss類的MRO
(<class '__main__.FatBoss'>, <class '__main__.FatFather'>, <class 'object'>)

=========== 下面按照 MRO 順序執行super方法 =============
胖子老闆的類被呼叫啦!
FatFather的init開始被呼叫
呼叫FatFather類的name是胖子老闆
FatFather的init呼叫結束
胖子老闆 的愛好是 打鬥地主

從上面的結果 (<class '__main__.FatBoss'>, <class '__main__.FatFather'>, <class 'object'>) 可以看出,super() 方法在 FatBoss 會直接呼叫父類是 FatFather ,所以單繼承是沒問題的。

那麼如果多繼承的話,會有什麼問題呢?

多繼承使用super()

假設再寫一個胖子老闆的女兒類,和 胖子老闆的老婆類,此時女兒需要同時繼承 兩個類(胖子老闆類,胖子老闆老婆類)。

因為胖子老闆有一個愛好,胖子老闆的老婆需要幹活幹家務,那麼女兒需要幫忙同時兼顧。

此時女兒就是需要繼承使用這兩個父類的方法了,那麼該如何去寫呢?

下面來看看實現程式碼:

# 胖子老闆的父類
class FatFather(object):
    def __init__(self, name, *args, **kwargs):
        print()
        print("=============== 開始呼叫 FatFather  ========================")
        print('FatFather的init開始被呼叫')
        self.name = name
        print('呼叫FatFather類的name是%s' % self.name)
        print('FatFather的init呼叫結束')
        print()
        print("=============== 結束呼叫 FatFather  ========================")


# 胖子老闆類 繼承 FatFather 類
class FatBoss(FatFather):
    def __init__(self, name, hobby, *args, **kwargs):
        print()
        print("=============== 開始呼叫 FatBoss  ========================")
        print('胖子老闆的類被呼叫啦!')
        #super().__init__(name)
        ## 因為多繼承傳遞的引數不一致,所以使用不定引數
        super().__init__(name, *args, **kwargs)
        print("%s 的愛好是 %s" % (name, hobby))
        print()
        print("=============== 結束呼叫 FatBoss  ========================")


# 胖子老闆的老婆類 繼承 FatFather類
class FatBossWife(FatFather):
    def __init__(self, name, housework, *args, **kwargs):
        print()
        print("=============== 開始呼叫 FatBossWife  ========================")
        print('胖子老闆的老婆類被呼叫啦!要學會幹家務')
        #super().__init__(name)
        ## 因為多繼承傳遞的引數不一致,所以使用不定引數
        super().__init__(name, *args, **kwargs)
        print("%s 需要乾的家務是 %s" % (name, housework))
        print()
        print("=============== 結束呼叫 FatBossWife  ========================")


# 胖子老闆的女兒類 繼承 FatBoss FatBossWife類
class FatBossGril(FatBoss, FatBossWife):
    def __init__(self, name, hobby, housework):
        print('胖子老闆的女兒類被呼叫啦!要學會幹家務,還要會幫胖子老闆鬥地主')
        super().__init__(name, hobby, housework)


def main():

    print("列印FatBossGril類的MRO")
    print(FatBossGril.__mro__)

    print()
    print("=========== 下面按照 MRO 順序執行super方法 =============")
    gril = FatBossGril("胖子老闆", "打鬥地主", "拖地")

執行結果如下:

if __name__ == "__main__":
    main()
列印FatBossGril類的MRO
(<class '__main__.FatBossGril'>, <class '__main__.FatBoss'>, <class '__main__.FatBossWife'>, <class '__main__.FatFather'>, <class 'object'>)

=========== 下面按照 MRO 順序執行super方法 =============
胖子老闆的女兒類被呼叫啦!要學會幹家務,還要會幫胖子老闆鬥地主

=============== 開始呼叫 FatBoss  ========================
胖子老闆的類被呼叫啦!

=============== 開始呼叫 FatBossWife  ========================
胖子老闆的老婆類被呼叫啦!要學會幹家務

=============== 開始呼叫 FatFather  ========================
FatFather的init開始被呼叫
呼叫FatFather類的name是胖子老闆
FatFather的init呼叫結束

=============== 結束呼叫 FatFather  ========================
胖子老闆 需要乾的家務是 拖地

=============== 結束呼叫 FatBossWife  ========================
胖子老闆 的愛好是 打鬥地主

=============== 結束呼叫 FatBoss  ========================

從上面的執行結果來看,我特意給每個類的呼叫開始以及結束都進行列印標識,可以看到。

每個類開始呼叫是根據MRO順序進行開始,然後逐個進行結束的。

還有就是由於因為需要繼承不同的父類,引數不一定。

所以,所有的父類都應該加上不定引數*args , **kwargs ,不然引數不對應是會報錯的。

注意事項

  • super().__init__相對於類名.__init__,在單繼承上用法基本無差

  • 但在多繼承上有區別,super方法能保證每個父類的方法只會執行一次,而使用類名的方法會導致方法被執行多次,可以嘗試寫個程式碼來看輸出結果

  • 多繼承時,使用super方法,對父類的傳引數,應該是由於python中super的演算法導致的原因,必須把引數全部傳遞,否則會報錯

  • 單繼承時,使用super方法,則不能全部傳遞,只能傳父類方法所需的引數,否則會報錯

  • 多繼承時,相對於使用類名.__init__方法,要把每個父類全部寫一遍, 而使用super方法,只需寫一句話便執行了全部父類的方法,這也是為何多繼承需要全部傳參的一個原因

練習

以下的程式碼的輸出將是什麼? 說出你的答案並解釋。

class Parent(object):
    x = 1


class Child1(Parent):
    pass


class Child2(Parent):
    pass


print(Parent.x, Child1.x, Child2.x)
1 1 1
Child1.x = 2
print(Parent.x, Child1.x, Child2.x)
1 2 1
  • 注意:Child1已經擁有了屬於自己的x
Parent.x = 3
print(Parent.x, Child1.x, Child2.x)
3 2 3

相關文章