Python面向對象三要素-繼承(Inheritance)
作者:尹正傑
版權聲明:原創作品,謝絕轉載!否則將追究法律責任。
一.繼承概述
1>.基本概念
前面我們學習了Python的面向對象三要素之一,封裝。今天我們來學習一下繼承(Inheritance)
人類和貓類都繼承自動物類。
個體繼承自父類,繼承了父類的一部分特征,但也可以有自己的個性。
再面向對象的世界中,從父類繼承,就可以直接擁有父類的屬性方法,這樣可以減少代碼,多復用。子類可以定義自己的屬性和方法。
2>.看一個不用繼承的例子
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 7 class Animal: 8 def shout(self): 9 print("Animal shouts") 10 11 12 class Cat: 13 def shout(self): 14 print("Cat shouts") 15 16 17 a = Animal() 18 a.shout() 19 20 c = Cat() 21 c.shout() 22 23 24 25 #以上代碼執行結果如下: 26 Animal shouts 27 Cat shouts
3>. 使用繼承的方式改良上一個不用繼承的案例
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 7 class Animal: 8 def __init__(self,name): 9 self._name = name 10 11 def shout(self): #定義一個通用的吃方法 12 print("{} shouts".format(self.__class__.__name__)) 13 14 @property 15 def name(self): 16 return self._name 17 18 class Cat(Animal): 19 pass 20 21 class Dog(Animal): 22 pass 23 24 a = Animal("monster") 25 a.shout() 26 27 cat = Cat("Kitty") 28 cat.shout() 29 print(cat.name) 30 31 dog = Dog("二哈") 32 dog.shout() 33 print(dog.name) 34 35 36 #以上代碼執行結果如下: 37 Animal shouts 38 Cat shouts 39 Kitty 40 Dog shouts 41 二哈

1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 class Document: 7 def __init__(self,content): 8 self.content = content 9 10 def print(self): #基類中只定義,不實現的方法,稱為“抽象方法”。在python中,如果采用這種方式定義的抽象方法,子類可以不實現,知道子類使用該方法的時候才報錯。 11 """ 12 基類提供的方法可以不具體實現,因為它未必適合子類的打印,子類中需要覆蓋重寫。 13 """ 14 raise NotImplementedError() 15 16 class Word(Document): 17 pass 18 19 class Pdf(Document): 20 pass
4>.總結
通過上例可以看出,通過繼承,貓類,狗類不用寫代碼,直接繼承了父類的屬性和方法。
繼承:
class Cat(Animal)這種形式就是從父類繼承,括號中寫上繼承的類的列表。
繼承可以讓子類從父類獲取特征(屬性和方法)
父類:
calss Animal就是Cat和Dog的父類,也稱為基類,超類。
子類:
Cat就是Animal的子類,也成為派生類。
二.繼承定義
1>.繼承使用格式
通過上面的案例,相比大家也可以總結出來繼承的使用格式:
class 子類名(基類1[,基類2,...])
和C++一樣,Python也支持多繼承,繼承也可以多級。
2>.在Python3中,object類是所有對象的根基類
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 7 class A: 8 pass 9 10 #如果類定義時,沒有基類列表,等同於繼承自object。 11 class A(object): 12 pass 13 14 15 """ 16 注意,上例在Python2中,兩種寫法時不同的。 17 Python支持多繼承,繼承也可以多級。 18 """
3>.查看繼承的特殊屬性和方法
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 7 class A: 8 pass 9 10 print(A.__base__) #類的基類。 11 print(A.__bases__) #類的基類元組。 12 print(A.__mro__) #顯示方法查找順序,基類的元組。 13 print(A.mro()) #同上,返回列表。 14 print(A.__subclasses__()) #類的子類列表。 15 16 17 18 #以上代碼執行結果如下: 19 <class 'object'> 20 (<class 'object'>,) 21 (<class '__main__.A'>, <class 'object'>) 22 [<class '__main__.A'>, <class 'object'>] 23 []
三.繼承中的訪問控制
1>.代碼案例
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 """ 7 從父類繼承,自己沒有的,就可以到父類中找。 8 私有的都是不可以訪問的,但是本質上依然是改了名稱放在這個屬性所在類的實例"__dict__"中。 9 知道這個新名稱就可以直接找到這個隱藏的變量,這是個黑魔法技巧,慎用。 10 """ 11 12 class Animal: 13 __COUNT = 100 14 HEIGHT = 0 15 16 def __init__(self,age,weight,height): 17 self.__COUNT += 1 18 self.age = age 19 self.__weight = weight 20 self.HEIGHT = height 21 22 def eat(self): 23 print("{} eat".format(self.__class__.__name__)) 24 25 def __getweight(self): 26 print(self.__weight) 27 28 @classmethod 29 def showcount1(cls): 30 print(cls) 31 print(cls.__dict__) 32 print(cls.__COUNT) 33 34 @classmethod 35 def __showcount2(cls): 36 print(cls.__COUNT) 37 38 def showcount3(self): 39 print(self.__COUNT) 40 41 class Cat(Animal): 42 NAME = 'CAT' 43 __COUNT = 200 44 45 46 c = Cat(3,5,15) 47 c.eat() 48 print(c.HEIGHT) 49 # print(c.__COUNT) #不能訪問,因為它屬於私有變量,該私有變量python內部做了處理,變量名稱被更改。 50 51 # print(c.__getweight()) #同上,這是私有方法,該私有方法python內部做了處理,變量名稱被更改。 52 53 c.showcount1() 54 55 # c.__showcount2() #無法直接訪問父類的私有方法,如果你非要訪問的話,可以使用“c._Animal__showcount2()” 56 57 c.showcount3() 58 59 print(c._Cat__COUNT) 60 61 print(c._Animal__COUNT) 62 63 print(c.NAME) 64 65 print("{}".format(Animal.__dict__)) 66 print("{}".format(Cat.__dict__)) 67 print(c.__dict__) 68 print(c.__class__.mro()) 69 70 71 72 #以上代碼執行結果如下: 73 Cat eat 74 15 75 <class '__main__.Cat'> 76 {'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None} 77 100 78 101 79 200 80 101 81 CAT 82 {'__module__': '__main__', '_Animal__COUNT': 100, 'HEIGHT': 0, '__init__': <function Animal.__init__ at 0x000001F6AB694438>, 'eat': <function Animal.eat at 0x000001F6AB6944C8>, '_Animal__getweight': <function Animal.__getweight at 0x000001F6AB694558>, 'showcount1': <classmethod object at 0x000001F6AB6BA388>, '_Animal__showcount2': <classmethod object at 0x000001F6AB6BA0C8>, 'showcount3': <function Animal.showcount3 at 0x000001F6AB6949D8>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None} 83 {'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None} 84 {'_Animal__COUNT': 101, 'age': 3, '_Animal__weight': 5, 'HEIGHT': 15} 85 [<class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>]
2>.總結
繼承時,公有的,子類和實例都可以隨意訪問;私有成員被隱藏,子類和實例不可直接訪問,但私有變量所在的類內的方法中可以訪問這個私有變量。
Python通過自己一套實現,實現和其它語言一樣的面向對象的繼承機制。
實例屬性查找順序:
實例的 "__dict__" ===> "類__dict__" ===> "父類__dict"
如果搜索這些地方后沒有找到就會拋出異常,先找到就立即返回了。
四.方法的重寫,覆蓋override
1>.super()可以訪問到父類的類屬性
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 class Animal: 7 def shout(self): 8 print("Animal shouts") 9 10 class Cat(Animal): 11 # def shout(self): #覆蓋了父類方法 12 # print("miao") 13 14 def shout(self): #覆蓋了自身的方法,顯式調用了父類的方法 15 print(super()) 16 print(super(Cat,self)) 17 print(super(self.__class__,self)) 18 19 super().shout() 20 super(Cat,self).shout() #等價於super() 21 self.__class__.__base__.shout(self) 22 23 a = Animal() 24 a.shout() 25 26 c = Cat() 27 c.shout() 28 29 print(a.__dict__) 30 print(c.__dict__) 31 print(Animal.__dict__) 32 print(Cat.__dict__) 33 34 35 36 37 #以上代碼執行結果如下: 38 Animal shouts 39 <super: <class 'Cat'>, <Cat object>> 40 <super: <class 'Cat'>, <Cat object>> 41 <super: <class 'Cat'>, <Cat object>> 42 Animal shouts 43 Animal shouts 44 Animal shouts 45 {} 46 {} 47 {'__module__': '__main__', 'shout': <function Animal.shout at 0x000001DD6FB85678>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None} 48 {'__module__': '__main__', 'shout': <function Cat.shout at 0x000001DD6FB851F8>, '__doc__': None}
2>.類方法和靜態方法覆蓋
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 """ 7 這些方法都可以覆蓋,原理都一樣,屬性字典的搜索順序。 8 """ 9 10 class Animal: 11 12 @classmethod 13 def class_method(cls): 14 print("class_method_animal") 15 16 @staticmethod 17 def static_method(): 18 print("static_method_animal") 19 20 class Cat(Animal): 21 22 @classmethod 23 def class_method(cls): 24 print("class_method_cat") 25 26 @staticmethod 27 def static_method(): 28 print("static_method_cat") 29 30 c = Cat() 31 c.class_method() 32 c.static_method() 33 34 print(Cat.__dict__) 35 print(Animal.__dict__) 36 37 Cat.static_method() 38 Animal.static_method() 39 40 41 42 #以上代碼執行結果如下: 43 class_method_cat 44 static_method_cat 45 {'__module__': '__main__', 'class_method': <classmethod object at 0x0000019B18317588>, 'static_method': <staticmethod object at 0x0000019B183175C8>, '__doc__': None} 46 {'__module__': '__main__', 'class_method': <classmethod object at 0x0000019B183174C8>, 'static_method': <staticmethod object at 0x0000019B18317508>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None} 47 static_method_cat 48 static_method_animal
五.繼承時使用初始化
1>.手動調用父類的構造方法
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 7 class A: 8 def __init__(self,a,d = 10): 9 self.a = a 10 self.__d = d 11 12 class B: 13 def __init__(self,b,c): 14 A.__init__(self,b + c,b - c) #我們調用了A的構造方法,那么就可以使用它的屬性啦。 15 self.b = b 16 self.c = c 17 18 def printv(self): 19 print(self.b) 20 print(self.a) 21 22 23 f = B(200,300) 24 print(f.__dict__) 25 print(f.__class__.__bases__) 26 f.printv() 27 28 29 30 #以上代碼執行結果如下: 31 {'a': 500, '_A__d': -100, 'b': 200, 'c': 300} 32 (<class 'object'>,) 33 200 34 500
2>.自動調用父類的構造方法
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 class Animal: 7 def __init__(self,age): 8 print("init in Animal") 9 self.age = age 10 11 def show(self): 12 print(self.age) 13 14 class Cat(Animal): 15 def __init__(self,age,weight): 16 #調用父類的__init__方法的順序有時決定着show方法的結果 17 super().__init__(age) 18 print("init in Cat") 19 self.age = age + 1 20 self.weight = weight 21 # super().__init__(age) #調用父類的方法其實也可以不用放在第一行,在Java語言中必須放在構造方法的首行。 22 23 c = Cat(10,5) 24 c.show() 25 26 27 28 #以上代碼執行結果如下: 29 init in Animal 30 init in Cat 31 11
3>.屬性的繼承說明
一個原則,自己的私有屬性,就該自己的方法讀取和修改,不要借助其它類的方法,即使是父類或者派生類的方法。
六.多繼承
1>.Python不同版本的類概述
Python2.2之前類時沒有共同祖先的,之后,引入object類,它時所有類的共同祖先類object。
Python2.7.X中為了兼容,分為古典類(舊式類)和新式類。
Python3中全部都是新式類。
新式類都是繼承自object,新式類可以使用super。
2>.Python多繼承實現
在面向對象這種,父類,子類通過繼承聯系在一起,如果可以通過一套方法,就可以實現不同表現,就是多態。一個類繼承自多個類就是多繼承,它將具有多個類的特征。 多繼承畢竟會帶來路徑選擇問題,究竟繼承哪個父類的特征呢?如上圖所示,左圖是多繼承(菱形繼承),右圖是單一繼承。 Python使用MRO(method resolution order方法解析順序)解決基類搜索順序問題。 歷史原因,MRO有三個搜素算法: 經典算法,按照定義從左到右,深度優先策略。2.2版本之前左圖的MRO是MyClass->D->B->A->C->A 新式類算法,是經典算法的升級,深度優先,重復的只保留最后一個。2.2版本左圖的MRO是MyClass->D->B->C->A->object C3算法,在類被創建出來的時候,就計算一個MRO有序列表。2.3之后,Python3唯一支持的算法左圖中的MRO是MyClass->D->B->C-A->object的列表。C3算法解決多繼承的二義性。 經典算法有很大的問題,如果C中有覆蓋A的方法,也不會訪問到它,因為先訪問A的(深度優先)。 新式類算法,依然采用了深度優先,解決重復問題,但是同經典算法一樣,沒有解決繼承的單調性。 C3算法,解決了繼承的單調性,它阻止創建之前產生二義性的代碼。求得的MRO本質是為了線性化,且確定了順序。 單調性:假設有A,B,C三個類,C的mro是[C,A,B],那么C的子類的mro中,A,B的順序一致就是單調的。
3>.多繼承的缺點
多繼承很好的模擬了世界,因為事物很少是單一繼承,但是舍棄簡單,必然引入復雜性,帶來了沖突。
如同一個孩子繼承了來自父母雙方的特征。那么到底眼睛像爸爸還是媽媽呢?孩子究竟該像誰多一點呢?
多繼承的實現會導致編譯器設計的復雜度增加,所以有些高級編程語言舍棄了類的多繼承。
C++支持多繼承;Java舍棄了多繼承。
Java中,一個類可以實現多個接口,一個接口也可以繼承多個接口。Java的接口很純粹,只是方法的聲明,繼承者必須實現這些方法,就具有了這些能力。就能干什么。
多繼承可能會帶來二義性,例如,貓和狗都繼承自動物類,現在如果一個多繼承了貓和狗類,貓和狗都有shout方法,子類究竟繼承誰的shout呢?解決方案:實現多繼承的語言,要解決二義性,深度優先或者廣度優先。
當類很多,繼承復雜的情況下,繼承路徑太多,很難說清什么樣的繼承路徑。
Python語法時允許多繼承,但Python代碼時解釋執行,只是執行到的時候才發現錯誤。
團隊協作開發,如果引入多繼承,那代碼很可能不可控。
不管編程語言是否支持多繼承,都應當避免多繼承。
Python的面向對象,我們看到的太靈活了,太開放了,所以要團隊守規矩。
七.Mimin類
1>.單繼承存在的弊端案例
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 class Document: 7 def __init__(self,content): 8 self.content = content 9 10 def print(self): #基類中只定義,不實現的方法,稱為“抽象方法”。在python中,如果采用這種方式定義的抽象方法,子類可以不實現,知道子類使用該方法的時候才報錯。 11 """ 12 基類提供的方法可以不具體實現,因為它未必適合子類的打印,子類中需要覆蓋重寫。 13 """ 14 raise NotImplementedError() 15 16 class Word(Document): 17 pass 18 19 class Pdf(Document): 20 pass 21 22 23 """ 24 拋出問題: 25 從上面的案例可以看出print算是一種能力(打印功能),不是所有的Document的子類都需要的,所以從這個角度觸發,上面的基類Document設計有點問題。 26 27 解決思路: 28 如果在現有子類Word或Pdf上直接增加,雖然可以,卻違反了OCP的原則(多用“繼承”,少修改),所以可以繼承后增加打印功能。 29 30 在這個時候發現,為了增加一種能力,就要增加一次繼承,類可能太多了,繼承的方式不是很好了。 31 32 功能太多,A類需要某幾樣功能,B類需要另幾樣功能,他們需要的是多個功能的自由組合,繼承實現很繁瑣。 33 34 我們可以引入類裝飾器來解決問題,裝飾器的有點在於: 35 簡單方便,在需要的地方動態增加,直接使用裝飾器 36 可以為類靈活的增加功能。 37 38 但是類裝飾器不可繼承,因此我們引入Mixin方案,Mixin就是其它類混合進來,同時帶來了類的屬性和方法。 39 40 Mixin類本質上就是多繼承實現的。Mixin體現的是一種組合的設計模式。 41 """
2>.Mixin案例展示
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 class Document: #假設該類為第三方庫,不允許修改 7 def __init__(self,content): 8 self.content = content 9 10 11 class Word(Document): #假設該類為第三方庫,不允許修改 12 pass 13 14 class Pdf(Document): #假設該類為第三方庫,不允許修改 15 pass 16 17 18 19 def printable(cls): #定義類裝飾器 20 def _print(self): 21 print(self.content,"裝飾器") 22 cls.print = _print 23 return cls 24 25 @printable 26 class PrintablePdf(Word): #使用類裝飾器和Mixin進行使用上的對比 27 pass 28 29 print(PrintablePdf.__dict__) 30 print(PrintablePdf.mro()) 31 32 33 class PrintableMimin: #Mixin就是其它類混合進來,同時帶來了類的屬性和方法,這里看來和裝飾器效果一樣,也沒有什么特別的。但是Mixin是類,就可以繼承。 34 def print(self): 35 print(self.content,"Mixin") 36 37 class PrintableWord(PrintableMimin,Word): #Mixin類本質上就是多繼承實現的。Mixin體現的是一種組合的設計模式。 38 pass 39 40 print(PrintableWord.__dict__) 41 print(PrintableWord.mro()) 42 43 44 pw = PrintableWord("test string") 45 pw.print() 46 47 class SuperPrintableMixin(PrintableMimin): 48 def print(self): 49 print("{0} 打印增強前 {0}".format("*" * 20)) 50 super().print() 51 print("{0} 打印增強后 {0}".format("*" * 20)) 52 53 54 class SuperPrintablePdf(SuperPrintableMixin,Pdf): 55 pass 56 57 print(SuperPrintablePdf.__dict__) 58 print(SuperPrintablePdf.mro()) 59 60 spp = SuperPrintablePdf("super print pdf") 61 spp.print() 62 63 64 65 #以上代碼執行結果如下: 66 {'__module__': '__main__', '__doc__': None, 'print': <function printable.<locals>._print at 0x0000016E8C6459D8>} 67 [<class '__main__.PrintablePdf'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>] 68 {'__module__': '__main__', '__doc__': None} 69 [<class '__main__.PrintableWord'>, <class '__main__.PrintableMimin'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>] 70 test string Mixin 71 {'__module__': '__main__', '__doc__': None} 72 [<class '__main__.SuperPrintablePdf'>, <class '__main__.SuperPrintableMixin'>, <class '__main__.PrintableMimin'>, <class '__main__.Pdf'>, <class '__main__.Document'>, <class 'object'>] 73 ******************** 打印增強前 ******************** 74 super print pdf Mixin 75 ******************** 打印增強后 ********************
3>.Mixin類的使用原則
在面向對象的設計中,一個復雜的類,往往需要很多功能,而這些功能有來自抓不同的類提供,這就需要很多的類組合在一起。
從設計模式的角度來說,應該多組合,少繼承。
Mixin類的使用原則:
Mixin類中不應該顯式的出現__init__初始化方法。
Mixin類通常不能獨立工作,因為它式准備混入別的類中的部分功能實現。
Mixin類的祖先類也應該式Mixin類。
使用時,Mixin類通常在繼承列表的第一個位置,例如上來中的"class PrintableWord(PrintableMixin,Word):pass"
Mixin類和裝飾器:
這兩種方式都可以使用,看個人喜好。
如果還需要繼承就得使用Mixin類的方式。