小白都能理解的Python多繼承

前言

本文主要作科普用,在真實編程中不建議使用多重繼承,或者少用多重繼承,避免使代碼難以理解。python

方法解析順序(MRO)

關於多重繼承,比較重要的是它的方法解析順序(能夠理解爲類的搜索順序),即MRO。這個跟類是新式類仍是經典類有關,由於二者的搜索算法不一樣。算法

在Python2及之前的版本,由任意內置類型派生出的類(只要一個內置類型位於類樹的某個位置),都屬於新式類;反之,不禁任意內置類型派生出的類,則稱之爲經典類編程

在Python3之後,沒有該區分,全部的類都派生自內置類型object,無論有沒有顯式繼承object,都屬於新式類spa

對於經典類,多重繼承的MRO是深度優先,即從下往上搜索;新式類的MRO是採用C3算法(不一樣狀況下,可表現爲廣度優先,也可表現爲深度優先)code

C3算法表現爲深度優先的例子:cdn

# C3-深度優先(D -> B -> A -> C)
class A:
    var = 'A var'


class B(A):
    pass


class C:
    var = 'C var'


class D(B, C):
    pass


if __name__ == '__main__':
    # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>, <class 'object'>]
    print(D.mro())
    # A var
    print(D.var)
複製代碼

C3算法表現爲廣度優先的例子:blog

# C3-廣度優先(D -> B -> C -> A)
class A:
    var = 'A var'


class B(A):
    pass


class C(A):
    var = 'C var'


class D(B, C):
    pass


if __name__ == '__main__':
    # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
    print(D.mro())
    # C var
    print(D.var)

複製代碼

注:關於C3的詳細算法本文不討論,由於我也搞不懂(狗頭保命)繼承

菱形多重繼承

其實菱形多重繼承上面已經有例子了,就是C3算法表現爲廣度優先這個例子,畫起圖來是這樣的: ip

Xnip2020-07-11_00-16-45.jpg

菱形多重繼承會致使的一個問題是A初始化了兩次,以下:get

class A:
    def say(self):
        print("A say")


class B(A):
    def say(self):
        print("B say")
        A.say(self)


class C(A):
    def say(self):
        print("C say")
        A.say(self)


class D(B, C):
    def say(self):
        print("D say")
        B.say(self)
        C.say(self)


if __name__ == '__main__':
    dd = D()
    dd.say()

複製代碼

若是隻想調用一次A,可以使用super方法:

class A:
    def say(self):
        print("A say")


class B(A):
    def say(self):
        print("B say")
        super().say()


class C(A):
    def say(self):
        print("C say")
        super().say()


class D(B, C):
    def say(self):
        print("D say")
        super().say()


if __name__ == '__main__':
    print(D.mro())
    dd = D()
    dd.say()

複製代碼

_init_ 與 super()

1.若是父類有init方法,子類沒有,則子類默認繼承父類的init方法

class A:
    def __init__(self, a1, a2):
        self.a1 = a1
        self.a2 = a2

    def say(self):
        print("A say, a1: %s, a2: %s" % (self.a1, self.a2))


class B(A):
    def say(self):
        print("B say, a1: %s, a2: %s" % (self.a1, self.a2))


if __name__ == '__main__':
    # 由於B繼承了A的init方法,因此也要傳入 a1,a2參數
    bb = B("10", "100")
    bb.say()

複製代碼

2.若是父類有init方法,子類也有,可理解爲子類重寫了父類的init方法

class A:
    def __init__(self, a1, a2):
        self.a1 = a1
        self.a2 = a2

    def say(self):
        print("A say, a1: %s, a2: %s" % (self.a1, self.a2))


class B(A):
    def __init__(self, b1):
        self.b1 = b1

    def say(self):
        print("B say, b1: %s" % self.b1)


if __name__ == '__main__':
    # 此處B重寫了A的init方法,因此只須要傳入b1參數,也沒有擁有a1,a2屬性
    bb = B("10")
    bb.say()

複製代碼

3.對於第二點,爲了能使用或者擴展父類的行爲,更常見的作法是在重寫init方法的同時,顯示調用父類的init方法(意味傳的參數要變成3個)。

# 三種寫法
class A:
    def __init__(self, a1, a2):
        self.a1 = a1
        self.a2 = a2

    def say(self):
        print("A say, a1: %s, a2: %s" % (self.a1, self.a2))


class B(A):
    def __init__(self, b1, a1, a2):
        # 第一種寫法: Python2的寫法
        # super(B, self).__init__(a1, a2)
        # 第二種寫法(推薦):Python3的寫法,與第一種等價
        super().__init__(a1, a2)
        # 第三種寫法:與前兩種等價,不過這種須要顯示調用基類,而第二種不用
        # A.__init__(self, a1, a2)
        self.b1 = b1

    def say(self):
        print("B say, a1: %s, a2: %s, b1: %s" % (self.a1, self.a2, self.b1))


if __name__ == '__main__':
    # 此處B重寫了A的init方法,因此只須要傳入b1參數,也沒有擁有a1,a2屬性
    bb = B("10", "100", "1000")
    bb.say()
複製代碼

最後的提醒

注意 __init__ 方法不要寫錯,避免寫成__ini__或者其餘的,由於兩個是在太像,出了問題很難排查(坑過兩次)。

參考

python 多重繼承的事
小心掉進Python多重繼承裏的坑

相關文章
相關標籤/搜索