Python面向對象篇(2)-繼承

  在發表本篇隨筆的時候,距離上一次發已經有一個多月了,不少朋友私信我爲何不持續更新了,在這裏先跟你們說聲抱歉。由於年末的工做較爲繁重,實在分不出精力,更重要的也是在思考後面進階的部分要按怎樣的順序寫,對於初學者來講更友好,更容易理解,但願個人文章能幫到更多的喜歡python,想要學習python的人,前面的文章我也會及時更新知識點和排版,2018年但願喜歡我文章的人能繼續支持,謝謝你們!python

一、 繼承 

1.1  繼承的實現

  對一個程序員來講,若是他的代碼中出現大量重複的代碼,那麼很快就會面臨被開除了。函數能夠有效的避免代碼的重複,而繼承,也能在最大程度解決這一問題,若是兩個類有不少相同的屬性,就能夠經過繼承來實現。程序員

  繼承指的是類與類之間的關係,在Python中,繼承分爲單繼承和多繼承,便可以同時繼承一個或多個類,被繼承的類稱爲基類或超類,繼承其餘類的類稱爲子類或派生類。繼承的方式很簡單,經過「class+子類(父類)」的方式來實現,以下:函數

1 class parent_class_1:
2     pass                 #建立父類1
3 class parent_class_2:
4     pass                 #建立父類2
5 class sub1_class(parent_class_1):
6     pass                 #建立子類sub_class,繼承父類parent_class_1(單繼承)
7 class sub2_class(parent_class_1,parent_class_2):
8     pass                 #建立子類sub_class,經過「,」來分割,繼承多個父類

 

  在使用多繼承的時候,若是繼承的全部父類中,都有相同的方法名,即父類1中有write()方法,父類2中也有一個write()方法,當一個類同時繼承這兩個類,若是子類調用調用write()方法,該調用哪一個父類的write()方法?此時會遵循一個原則,先繼承的類中的方法會重寫後繼承的類中的方法(使其不可訪問),因此注意繼承父類的順序是很重要的。多繼承的方法也是不建議多使用的。學習

  經過__base__方法和__bases__方法來查看繼承的父類是哪個。spa

  __base__:從左到右查看子類第一個繼承的父類,只返回第一個父類;code

  __bases__:查看子類繼承的全部類,返回繼承的全部父類。對象

1 print(sub1_class.__base__)    #查看sub1_class繼承的第一個父類
2 
3 print(sub2_class.__bases__)   #查看sub2_class 都繼承了哪些父類

  運行結果:blog

1 <class '__main__.parent_class_1'>
2 
3 (<class '__main__.parent_class_1'>, <class'__main__.parent_class_2'>)

  繼承,就像是繼承家產同樣,父親有的東西,繼承過來本身就也有了,因此,當一個類繼承了某一個父類的話,那麼這個子類就能夠調用繼承的父類中的全部屬性。能夠看下以下這個簡單的例子:繼承

 1 class parent_class:               #建立父類
 2     money = 1000000000            #定義一個類屬性:money
 3     def cost(self):               #定義一個方法:cost()
 4         print("若是你繼承了,個人錢你均可以花")
 5 class sub_class(parent_class):    #建立子類,繼承parent_class這個父類
 6     pass                          #類代碼邏輯爲空
 7 print(sub_class.money)            #經過子類,調用父類的屬性
 8 sub_class.cost(sub_class)         #經過子類,調用父類的方法
 9 運行結果:
10 1000000000
11 若是你繼承了,個人錢你均可以花

  在上面這個例子中,在建立父類parent_class的時候,定義了一個類屬性和類方法,而在建立子類sub_class的時候,類裏面沒有寫任何邏輯代碼,僅僅是在建立子類的時候繼承了父類,那麼也就意味着,這個子類就能夠調用父類中的全部屬性和方法。接口

1..2  繼承的基本原理和繼承順序

  在Python中,對於定義的每個類,Python內部都會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的全部基類的線性順序列表。

  Python若是繼承了多個類,那麼在繼承的順序上,遵循兩種方式:深度優先和廣度優先,以下圖:

   

  當類是經典類的時候,多繼承的狀況下,會按照深度優先的方式查找

  當類是新式類的時候,多繼承的狀況下,會按照廣度優先的方式查找

  在Python3中,建立的類都是新式類,因此都會按照廣度有限的方式進行查找什麼是廣式類呢?能夠理解爲:橫向優先檢索。以下:

 1 class A:
 2     def test(self):
 3         print("A")
 4 class B(A):
 5      def test(self):
 6          print("B")
 7 class C(A):
 8     def test(self):
 9         print("C")
10 class D(C,B):
11     pass
12 #__mro__方法,打印出類繼承的線性順序列表
13 print(D.__mro__) 
14 D.__mro__運行結果15 (<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

  在繼承順序上,Python內部會在mro列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類爲止,mro線性列表包含如下兩個原則:

  1)子類優於父類,即若是在D類定義了test函數,那麼它會優先調用本身類內部的test方法

  2)繼承多個父類會根據它們在列表中的順序被子類繼承,即若是是class D(B,C)那麼繼承順序就會變爲D->B->C->A

1.3  派生

  子類也叫作派生類,在繼承了父類的全部屬性和方法後,也容許添加、修改或從新定義本身內部的屬性和方法,若是新定義的屬性或方法與繼承來的父類中的屬性和方法重名時,在調用時會優先調用本身內部的屬性或方法,對父類中的代碼不會有任何的影響。

1 class Dog:
2     def jump(self):
3         print("我是父類:我很是愛跳")
4 class Tidy(Dog):
5     pass
6 s_1 = Tidy()
7 s_1.jump()
8 運行結果:
9 我是父類:我很是愛跳

  在這段代碼中,建立了一個父類Dog,並定義了一個方法jump(),接下來建立了一個子類(派生類)來繼承Dog類,子類內部沒有寫任何邏輯代碼,經過實例s_1調用父類中的方法jump()。運行結果如上所示。

  若是在子類中也定義了一個方法名是jump()的方法呢?調用時該運行哪個?

 1 class Dog:
 2     def jump(self):
 3         print("我是父類:很是愛跳")
 4 class Tidy(Dog):
 5     def jump(self):
 6         print("我是子類:很是愛跳")
 7 s_1 = Tidy()
 8 s_1.jump()
 9 運行結果:
10 我是子類:很是愛跳

  實例s_1是類Tidy()的實例化對象,當調用類的屬性或方法時,會優先在所屬類的內部查找,若是找不到,纔會去繼承的父類中查找,運行結果如上所示。

  內建的isinstance()函數和issubclass()函數的使用

  isinstance(x, A_tuple):判斷一個對象是不是某一個數據類型,返回一個布爾值。傳入兩個值,第一個值是須要判斷的對象,第二個值是數據類型。如判斷實例s_1是否是屬於類Dog:isinstance(s_1,Dog)

  issubclass(x, A_tuple):判斷一個類是否是另外一個類的子類,返回布爾值,傳入兩個值,第一個值子類的類名,第二個值是父類的類名。如判斷Tidy是否是Dog的子類:issubclass(Tidy,Dog)

  下面再來一個稍微複雜一點的。

 1 class Foo:
 2     def f1(self):
 3         print("Foo.f1")
 4     def f2(self):
 5         print("Foo.f2")
 6         self.f1()
 7 class Bar(Foo):
 8     def f1(self):
 9         print("Bar.f1")
10 s_1 = Bar()
11 s_1.f2()

  這段代碼,運行結果是什麼?爲何是這個結果?

1.4  組合

  經過組合的方式,也能夠避免代碼的大量重複,組合指的就是,在一個類中,以另外一個類做爲它的數據屬性。當類被定義後,

1 class Dog:                   #建立一個小狗的類
2     def jump(self):        #定義一個jump()方法
3         print("小狗正在跳")
4 class Tidy:
5     def __init__(self,name): #構造函數
6         self.name = name
7         self.jump = Dog()    #經過組合的方式,將Dog類賦值給self.jump屬性
8 s_1 = Tidy("AA")
9 s_1.jump.jump()           #調用Dog類中的jump()方法

  這就是兩個類之間的組合,能夠經過將類賦值給其餘類的一個數據屬性,在調用的時候,s1_jump,實質上就是在調用Dog這個類,再經過.jump()方法,實質上也就是—》Dog.jump,這樣就不難理解s_1.jump.jump()這條代碼了。

  組合和繼承都能很好、有效的利用已定義好的資源,不用重複去寫相同的代碼,可是兩者的使用場景也有一下不一樣。繼承能夠理解成包含的意思,當類之間有不少共同的屬性時,就比較適合將這些共有的屬性作成一個基類來繼承。好比,人都有一個鼻子,兩隻眼睛..不一樣的是高矮胖瘦…..;而組合,能夠理解爲。

 1 class School:
 2     def __init__(self,name,addr):
 3         self.name = name
 4         self.addr = addr
 5 class Course:
 6     def __init__(self,name,period,school):
 7         self.name = name
 8         self.period = period
 9         self.school = school
10 
11 s_1 = School("一中","小石路")
12 s_2 = School("二中","中石路")
13 s_3 = School("三中","大石路")
14 
15 msg="""
16 1:一中
17 2:二中
18 3:三中
19 """
20 
21 while True:
22     print(msg)
23     menu = {
24         "1":s_1,
25         "2":s_2,
26         "3":s_3
27     }
28 
29     choice = input("請輸入你的選擇:")
30     school_obj = menu[choice]
31     name = input("請輸入課程名")
32     period = input("請輸入學期")
33     new_course = Course(name,period,school_obj)
34     print("%s 屬於%s"%(new_course.name,new_course.school.name))

1.5  接口繼承

   在子類中繼承父類的方法

  繼承的最大做用在於能夠減小代碼的重複利用,子類繼承過來父類定義的數據屬性和方法後如何調用呢:

 1 class Vehicle:        
 2     def __init__(self,name,type,speed,):
 3         self.name = name
 4         self.type = type
 5         self.speed = speed
 6 
 7 
 8 class Bus(Vehicle):
 9     def __init__(self,name,type,speed,line):
10         Vehicle.__init__(self,name,type,speed)
11         self.line = line
12     def run(self):
13         print("%s路公交車立刻就到了"%self.line)
14 s_1 = Bus("公家車","自然氣","10km/h",1)
15 s_1.run()
16 運行結果:
17 1路公交車立刻就到了!

  這樣的方法有以下兩個缺點:第一:若是父類的名稱發生了變化,子類的代碼也要相應進行改變。第二:Python是容許多繼承的語言,如上所示的方法在多繼承中就要重複不少次調用父類的__init__()方法。爲了解決這些問題,就引入了super()機制。

 1 class Vehicle:
 2     def __init__(self,name,type,speed,):
 3         self.name = name
 4         self.type = type
 5         self.speed = speed
 6     def run(self):
 7         print("我是父類:開動了")
 8 
 9 
10 class Bus(Vehicle):
11     def __init__(self,name,type,speed,line):
12         # Vehicle.__init__(self,name,type,speed)
13         super(Bus,self).__init__(name,type,speed)
14         self.line = line
15     def run(self):
16         # Vehicle.run(self)
17         super().run()
18         print("%s路公交車立刻就到了"%self.line)
19 s_1 = Bus("公家車","自然氣","10km/h",1)
20 
21 s_1.run()

  從運行結果上來看,普通的這種繼承方式和super繼承是同樣的,但其實兩種方式的內部運行機制是不同的。在super機制裏能夠保證公共父類僅被執行一次,至於執行的順序,是按照mro進行的(類名.__mro__)。

  二者在運行機制上有什麼區別呢?

 1 class A:
 2     def __init__(self):
 3         print("test A")
 4 class B(A):
 5     def __init__(self):
 6         print("test B")
 7         A.__init__(self)
 8 class C(A):
 9     def __init__(self):
10         print("test C")
11         A.__init__(self)
12 class D(C,B):
13     def __init__(self):
14         print("test D")
15         B.__init__(self)
16         C.__init__(self)
17 D()
18 運行結果:
19 test D
20 test B
21 test A
22 test C
23 test A

   super

 1 class A:
 2     def __init__(self):
 3         print("test A")
 4 class B(A):
 5     def __init__(self):
 6         print("test B")
 7         super(B,self).__init__()
 8 class C(A):
 9     def __init__(self):
10         print("test C")
11         super(C,self).__init__()
12 class D(C,B):
13     def __init__(self):
14         print("test D")
15         super(D,self).__init__()
16 D()
17 運行結果:
18 test D
19 test C
20 test B
21 test A

  super(B,self).__init__()實質上就是調用了super類的初始化函數,產生一個super對象,僅僅是簡單的記錄了類的類型和具體的實例,super(類名, self).func的調用並非用於調用當前類的父類的func函數;Python的多繼承類是經過mro的方式來保證各個父類的函數被逐一調用,並且保證每一個父類函數  只調用一次(若是每一個類都使用super);混用super類和非綁定的函數是一個危險行爲,這可能致使應該調用的父類函數沒有調用或者一個父類函數被調用屢次。

相關文章
相關標籤/搜索