Python多重繼承引發的問題——牛逼的super
需要清楚的一個事情是:
任何類,都默認並隱式的繼承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