1. 程式人生 > >Python多重繼承引發的問題——牛逼的super

Python多重繼承引發的問題——牛逼的super

ont 這就是 ans lin 技術 wid 然而 多重繼承 obj

少說廢話多做事先上一個圖,此圖用win7下的畫圖工具所畫,當然,這不是重點

技術分享圖片

需要清楚的一個事情是:

任何類,都默認並隱式的繼承object類(根類),在上面的圖中,Transformers類同時繼承了Car和Ship類。那麽Car和Ship又隱式的繼承了object類,註意,這個object類不是我自己定義的

而是python的。


根據上面的圖,編寫代碼

class Car:
    #描述汽車
    call_car_record = 0 #記錄調用次數
    def __init__(self, car_name):
        self.car_name = car_name

    def land_run(self):
        print("{0} Run on land".format(self.car_name))
        self.call_car_record += 1

class Ship:
    #描述輪船
    call_ship_record = 0 #記錄調用次數
    def __init__(self, ship_name):
        self.ship_name = ship_name

    def running_in_sea(self):
        print("{0} Running in the sea".format(self.ship_name))
        self.call_ship_record += 1

class Transformers(Car, Ship):
    #變形金剛
    call_tran_record = 0 #記錄調用次數
    def __init__(self, car_name, ship_name, tran_name):
        Car.__init__(self, car_name)
        Ship.__init__(self, ship_name)
        self.tran_name = tran_name

    def transfiguration(self):
        print("{0} start Transfiguration...".format(self.tran_name))
        self.call_tran_record += 1

if __name__ == '__main__':

    t = Transformers("byd", "gz01", "toby")
    t.land_run()
    t.running_in_sea()
    t.transfiguration()
    print(t.call_tran_record, t.call_car_record, t.call_ship_record)

"""
輸出的結果:
    byd Run on land
    gz01 Running in the sea
    toby start Transfiguration...
    1 1 1
"""

那麽,上面的代碼,面臨的問題正是我在上圖中的描述,也就是說object類被隱式的調用了兩次,只是目前沒有察覺到,但是確實存在。

技術分享圖片


OK!!!我想證明這個問題,證實它是不是被調用兩次。我再上一個圖,如下:

技術分享圖片

上面的圖,就是傳說中的磚石繼承,啥是磚石繼承?只是這個類的繼承視圖看起來和磚石一樣,當然這只是我的理解。

如果用這個圖來證明BaseClass會不會被調用2次,那麽需要轉化成代碼。代碼如下:

class BaseClass:
    num_base_calls = 0 #記錄基類被調用次數
    def work(self):
        print("work method of calling base class")
        self.num_base_calls += 1

class AClass(BaseClass):
    num_aclass_calls = 0 #記錄被調用次數
    def work(self):
        BaseClass.work(self)
        print("work method of calling AClass class")
        self.num_aclass_calls += 1

class BClass(BaseClass):
    num_bclass_calls = 0 #記錄被調用次數
    def work(self):
        BaseClass.work(self)
        print("work method of calling BClass class")
        self.num_bclass_calls += 1

class CClass(AClass, BClass):
    num_cclass_calls = 0 #記錄被調用次數
    def work(self):
        AClass.work(self)
        BClass.work(self)
        print("Calling work method on CClass")
        self.num_cclass_calls += 1

if __name__ == '__main__':

    c = CClass()
    c.work()
    print(c.num_cclass_calls, c.num_aclass_calls, c.num_bclass_calls, c.num_base_calls)


"""
結果輸出:
work method of calling base class
work method of calling AClass class
work method of calling base class
work method of calling BClass class
Calling work method on CClass
1 1 1 2
"""

從輸出的結果來看,BaseClass真的被調用了兩次,這個基類是我自己定義的,這不是隱式調用,是明目張膽的在調用啊!!!

如何解決這個問題?專業人士說用super,我不信,所以我嘗試了一下,改進後的代碼如下:

class BaseClass:
    num_base_calls = 0 #記錄基類被調用次數
    def work(self):
        print("work method of calling base class")
        self.num_base_calls += 1

class AClass(BaseClass):
    num_aclass_calls = 0 #記錄被調用次數
    def work(self):
        super().work()
        print("work method of calling AClass class")
        self.num_aclass_calls += 1

class BClass(BaseClass):
    num_bclass_calls = 0 #記錄被調用次數
    def work(self):
        super().work()
        print("work method of calling BClass class")
        self.num_bclass_calls += 1

class CClass(AClass, BClass):
    num_cclass_calls = 0 #記錄被調用次數
    def work(self):
        super().work()
        print("Calling work method on CClass")
        self.num_cclass_calls += 1
if __name__ == '__main__':

    c = CClass()
    c.work()
    print(c.num_cclass_calls, c.num_aclass_calls, c.num_bclass_calls, c.num_base_calls)
"""
輸出結果:
work method of calling base class
work method of calling BClass class
work method of calling AClass class
Calling work method on CClass
1 1 1 1
"""

事實證明,BaseClass這個基類真的只被調用了一次,這就是super的威力。然而,不管你信不信,反正我是信了。

那分析一下他的調用順序,我又畫了個圖:

技術分享圖片

看圖分析:
1、CClass類的work()方法調用了super.work,其實是引用了AClass.work()
2、在AClass.work()中,調用了super.work(),這時候是引用了BClass.work(),而不是BaseClass.work(),這就是所謂的下一個方法
3、接著在BClass.work()中調用了super.work()方法,這時候才是去調用BaseClass.work()方法



現在回到第一個多重繼承的例子,在多重繼承的例子中,Transformers類的__init__方法中,為了實現能調用兩個父類的__init__()方法

我最初的做法是,分別寫了兩次調用。這裏面臨的問題是,有兩個父類的初始化方法要調用,而且還是需要不同的參數。

那麽,我一開始就寫成如下面代碼這樣,不是不行,而是不是我追求的目標,我是一個追求完美的人,所以我決定改進它,也是用super去做

class Transformers(Car, Ship):
    #變形金剛
    call_tran_record = 0
    def __init__(self, car_name, ship_name, tran_name):
        Car.__init__(self, car_name) #調用Car類的初始化方法
        Ship.__init__(self, ship_name) #調用Ship類的初始化方法
        self.tran_name = tran_name



改進後的代碼如下:

class Car:
    #描述汽車
    call_car_record = 0 #記錄調用次數
    def __init__(self, car_name=None, **kwargs):
        super().__init__(**kwargs)
        self.car_name = car_name

    def land_run(self):
        print("{0} Run on land".format(self.car_name))
        self.call_car_record += 1

class Ship:
    #描述輪船
    call_ship_record = 0 #記錄調用次數
    def __init__(self, ship_name=None, **kwargs):
        super().__init__(**kwargs)
        self.ship_name = ship_name

    def running_in_sea(self):
        print("{0} Running in the sea".format(self.ship_name))
        self.call_ship_record += 1

class Transformers(Car, Ship):
    #變形金剛
    call_tran_record = 0 #記錄調用次數
    def __init__(self, tran_name=None, **kwargs):
        super().__init__(**kwargs)
        self.tran_name = tran_name

    def transfiguration(self):
        print("{0} start Transfiguration...".format(self.tran_name))
        self.call_tran_record += 1

if __name__ == '__main__':

    t = Transformers(tran_name="toby", car_name="byd", ship_name="qq")
    t.land_run()
    t.running_in_sea()
    t.transfiguration()
    print(t.call_tran_record, t.call_car_record, t.call_ship_record)


關於如何分析它的執行順序?請用“下一個方法”的思路去理解它。


最後,再總結一下super的牛逼之處

1、在類的繼承層次結構中,只想調用"下一個方法",而不是父類的方法

2、super的目標就是解決復雜的多重繼承問題(基類被調用兩次的問題)

3、super是絕對的保證,在類的繼承層次結構中每一個方法只被執行一次

Python多重繼承引發的問題——牛逼的super