1. 繼承 1.1 什麼是繼承 繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為基類或超類,新建的類稱為派生類或子類。 python中類的繼承分為:單繼承和多繼承。 class ParentClass1: #定義父類 pass class ParentClas ...
1. 繼承
1.1 什麼是繼承
繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為基類或超類,新建的類稱為派生類或子類。
python中類的繼承分為:單繼承和多繼承。
class ParentClass1: #定義父類 pass class ParentClass2: #定義父類 pass class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類 pass
查看繼承:
>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個子類,__bases__則是查看所有繼承的父類 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果沒有指定基類,python的類會預設繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)
1.2 繼承與抽象
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:
1.將奧巴馬和梅西這倆對象比較像的部分抽取成類;
2.將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關註點,降低複雜度)
繼承:是基於抽象的結果,通過編程語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類
1.3 繼承與重用性
使用繼承來解決代碼重用的例子:
==========================第一部分 例如 貓可以:吃、喝、爬樹 狗可以:吃、喝、看家 如果我們要分別為貓和狗創建一個類,那麼就需要為 貓 和 狗 實現他們所有的功能,偽代碼如下: #貓和狗有大量相同的內容 class 貓: def 吃(self): # do something def 喝(self): # do something def 爬樹(self): # do something class 狗: def 吃(self): # do something def 喝(self): # do something def 看家(self): #do something ==========================第二部分 上述代碼不難看出,吃、喝是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次。如果使用 繼承 的思想,如下實現: 動物:吃、喝 貓:爬樹(貓繼承動物的功能) 狗:看家(狗繼承動物的功能) 偽代碼如下: class 動物: def 吃(self): # do something def 喝(self): # do something # 在類後面括弧中寫入另外一個類名,表示當前類繼承另外一個類 class 貓(動物): def 爬樹(self): print '喵喵叫' # 在類後面括弧中寫入另外一個類名,表示當前類繼承另外一個類 class 狗(動物): def 看家(self): print '汪汪叫' ==========================第三部分 #繼承的代碼實現 class Animal: def eat(self): print("%s 吃 " %self.name) def drink(self): print ("%s 喝 " %self.name) class Cat(Animal): def __init__(self, name): self.name = name self.breed = '貓' def climb(self): print('爬樹') class Dog(Animal): def __init__(self, name): self.name = name self.breed='狗' def look_after_house(self): print('汪汪叫') # ######### 執行 ######### c1 = Cat('小白家的小黑貓') c1.eat() c2 = Cat('小黑的小白貓') c2.drink() d1 = Dog('胖子家的小瘦狗') d1.eat()
在開發程式的過程中,如果我們定義了一個類A,然後又想新建立另外一個類B,但是類B的大部分內容與類A的相同時。
我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。
通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數據屬性和函數屬性),實現代碼重用。
class Animal: ''' 人和狗都是動物,所以創造一個Animal基類 ''' def __init__(self, name, aggressivity, life_value): self.name = name # 人和狗都有自己的昵稱; self.aggressivity = aggressivity # 人和狗都有自己的攻擊力; self.life_value = life_value # 人和狗都有自己的生命值; def eat(self): print('%s is eating'%self.name) class Dog(Animal): pass class Person(Animal): pass egg = Person('egon',10,1000) ha2 = Dog('二愣子',50,1000) egg.eat() ha2.eat()
提示:用已經有的類建立一個新的類,這樣就重用了已經有的軟體中的一部分設置大部分,大大生了編程工作量,這就是常說的軟體重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定製新的數據類型,這樣就是大大縮短了軟體開發周期,對大型軟體開發來說,意義重大。
1.4 派生
當然子類也可以添加自己新的屬性或者在自己這裡重新定義這些屬性(不會影響到父類),需要註意的是,一旦重新定義了自己的屬性且與父類重名,那麼調用新增的屬性時,就以自己為準了。
class Animal: ''' 人和狗都是動物,所以創造一個Animal基類 ''' def __init__(self, name, aggressivity, life_value): self.name = name # 人和狗都有自己的昵稱; self.aggressivity = aggressivity # 人和狗都有自己的攻擊力; self.life_value = life_value # 人和狗都有自己的生命值; def eat(self): print('%s is eating'%self.name) class Dog(Animal): ''' 狗類,繼承Animal類 ''' def bite(self, people): ''' 派生:狗有咬人的技能 :param people: ''' people.life_value -= self.aggressivity class Person(Animal): ''' 人類,繼承Animal ''' def attack(self, dog): ''' 派生:人有攻擊的技能 :param dog: ''' dog.life_value -= self.aggressivity egg = Person('egon',10,1000) ha2 = Dog('二愣子',50,1000) print(ha2.life_value) print(egg.attack(ha2)) print(ha2.life_value)
註意:像ha2.life_value之類的屬性引用,會先從實例中找life_value然後去類中找,然後再去父類中找...直到最頂級的父類。
在子類中,新建的重名的函數屬性,在編輯函數內功能的時候,有可能需要重用父類中重名的那個函數功能,應該是用調用普通函數的方式,即:類名.func(),此時就與調用普通函數無異了,因此即便是self參數也要為其傳值。
在python3中,子類執行父類的方法也可以直接用super方法。
class A: def hahaha(self): print('A') class B(A): def hahaha(self): super().hahaha() #super(B,self).hahaha() #A.hahaha(self) print('B') a = A() b = B() b.hahaha() super(B,b).hahaha()
class Animal: ''' 人和狗都是動物,所以創造一個Animal基類 ''' def __init__(self, name, aggressivity, life_value): self.name = name # 人和狗都有自己的昵稱; self.aggressivity = aggressivity # 人和狗都有自己的攻擊力; self.life_value = life_value # 人和狗都有自己的生命值; def eat(self): print('%s is eating'%self.name) class Dog(Animal): ''' 狗類,繼承Animal類 ''' def __init__(self,name,breed,aggressivity,life_value): super().__init__(name, aggressivity, life_value) #執行父類Animal的init方法 self.breed = breed #派生出了新的屬性 def bite(self, people): ''' 派生出了新的技能:狗有咬人的技能 :param people: ''' people.life_value -= self.aggressivity def eat(self): # Animal.eat(self) #super().eat() print('from Dog') class Person(Animal): ''' 人類,繼承Animal ''' def __init__(self,name,aggressivity, life_value,money): #Animal.__init__(self, name, aggressivity, life_value) #super(Person, self).__init__(name, aggressivity, life_value) super().__init__(name,aggressivity, life_value) #執行父類的init方法 self.money = money #派生出了新的屬性 def attack(self, dog): ''' 派生出了新的技能:人有攻擊的技能 :param dog: ''' dog.life_value -= self.aggressivity def eat(self): #super().eat() Animal.eat(self) print('from Person') egg = Person('egon',10,1000,600) ha2 = Dog('二愣子','哈士奇',10,1000) print(egg.name) print(ha2.name) egg.eat()
通過繼承建立了派生類與基類之間的關係,它是一種'是'的關係,比如白馬是馬,人是動物。
當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如教授是老師
>>> class Teacher: ... def __init__(self,name,gender): ... self.name=name ... self.gender=gender ... def teach(self): ... print('teaching') ... >>> >>> class Professor(Teacher): ... pass ... >>> p1=Professor('egon','male') >>> p1.teach() teaching
1.5 抽象類與介面類
1.5.1 介面類
繼承有兩種用途:
一:繼承基類的方法,並且做出自己的改變或者擴展(代碼重用) 。
二:聲明某個子類相容於某基類,定義一個介面類Interface,介面類中定義了一些介面名(就是函數名)且並未實現介面的功能,子類繼承介面類,並且實現介面中的功能。
class Alipay: ''' 支付寶支付 ''' def pay(self,money): print('支付寶支付了%s元'%money) class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) def pay(payment,money): ''' 支付函數,總體負責支付 對應支付的對象和要支付的金額 ''' payment.pay(money) p = Alipay() pay(p,200)
開發中容易出現的問題:
class Alipay: ''' 支付寶支付 ''' def pay(self,money): print('支付寶支付了%s元'%money) class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) class Wechatpay: def fuqian(self,money): ''' 實現了pay的功能,但是名字不一樣 ''' print('微信支付了%s元'%money) def pay(payment,money): ''' 支付函數,總體負責支付 對應支付的對象和要支付的金額 ''' payment.pay(money) p = Wechatpay() pay(p,200) #執行會報錯
介面初成:手動報異常:NotImplementedError來解決開發中遇到的問題。
class Payment: def pay(self): raise NotImplementedError class Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money) p = Wechatpay() #這裡不報錯 pay(p,200) #這裡報錯了
借用abc模塊來實現介面。
from abc import ABCMeta,abstractmethod class Payment(metaclass=ABCMeta): @abstractmethod def pay(self,money): pass class Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money) p = Wechatpay() #不調就報錯了
實踐中,繼承的第一種含義意義並不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。
繼承的第二種含義非常重要。它又叫“介面繼承”。
介面繼承實質上是要求“做出一個良好的抽象,這個抽象規定了一個相容介面,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定介面的所有對象”——這在程式設計上,叫做歸一化。
歸一化使得高層的外部使用者可以不加區分的處理所有介面相容的對象集合——就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是記憶體、磁碟、網路還是屏幕(當然,對底層設計者,當然也可以區分出“字元設備”和“塊設備”,然後做出針對性的設計:細緻到什麼程度,視需求而定)。
依賴倒置原則:
高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該應該依賴細節;細節應該依賴抽象。換言之,要針對介面編程,而不是針對實現編程。
為何要用介面:
在python中根本就沒有一個叫做interface的關鍵字,上面的代碼只是看起來像介面,其實並沒有起到介面的作用,子類完全可以不用去實現介面 ,如果非要去模仿介面的概念,可以藉助第三方模塊。
介面提取了一群類共同的函數,可以把介面當做一個函數的集合。
然後讓子類去實現介面中的函數。
這麼做的意義在於歸一化,什麼叫歸一化,就是只要是基於同一個介面實現的類,那麼所有的這些類產生的對象在使用時,從用法上來說都一樣。
歸一化,讓使用者無需關心對象的類是什麼,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
比如:我們定義一個動物介面,介面里定義了有跑、吃、呼吸等介面函數,這樣老鼠的類去實現了該介面,松鼠的類也去實現了該介面,由二者分別產生一隻老鼠和一隻松鼠送到你面前,即便是你分別不到底哪只是什麼鼠你肯定知道他倆都會跑,都會吃,都能呼吸。
再比如:我們有一個汽車介面,裡面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現了汽車介面,這樣就好辦了,大家只需要學會了怎麼開汽車,那麼無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函數調用)都一樣。
1.5.2 抽象類
什麼是抽象類?
與java一樣,python也有抽象類的概念但是同樣需要藉助模塊實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化。
為什麼要有抽象類?
如果說類是從一堆對象中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括數據屬性和函數屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。
從設計角度去看,如果類是從現實對象抽象而來的,那麼抽象類就是基於類抽象而來的。
從實現角度來看,抽象類與普通類的不同之處在於:抽象類中有抽象方法,該類不能被實例化,只能被繼承,且子類必須實現抽象方法。這一點與介面有點類似,但其實是不同的,即將揭曉答案。
在python中實現抽象類:
#一切皆文件 import abc #利用abc模塊實現抽象類 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定義抽象方法,無需實現功能 def read(self): '子類必須定義讀功能' pass @abc.abstractmethod #定義抽象方法,無需實現功能 def write(self): '子類必須定義寫功能' pass # class Txt(All_file): # pass # # t1=Txt() #報錯,子類沒有定義抽象方法 class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('文本數據的讀取方法') def write(self): print('文本數據的讀取方法') class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('硬碟數據的讀取方法') def write(self): print('硬碟數據的讀取方法') class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('進程數據的讀取方法') def write(self): print('進程數據的讀取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #這樣大家都是被歸一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
1.5.3 抽象類與介面類
抽象類的本質還是類,指的是一組類的相似性,包括數據屬性(如all_type)和函數屬性(如read、write),而介面只強調函數屬性的相似性。
抽象類是一個介於類和介面直接的一個概念,同時具備類和介面的部分特性,可以用來實現歸一化設計。
在python中,並沒有介面類這種東西,即便不通過專門的模塊定義介面,我們也應該有一些基本的概念。
1.多繼承問題
在繼承抽象類的過程中,我們應該儘量避免多繼承;
而在繼承介面的時候,我們反而鼓勵你來多繼承介面。
介面隔離原則:
使用多個專門的介面,而不使用單一的總介面。即客戶端不應該依賴那些不需要的介面。
2.方法的實現
在抽象類中,我們可以對一些抽象方法做出基礎實現;
而在介面類中,任何方法都只是一種規範,具體的功能需要子類實現。
1.6 繼承順序和原理
1.6.1 繼承順序
class A(object): def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D,E): # def test(self): # print('from F') pass f1=F() f1.test() print(F.__mro__) #只有新式才有這個屬性可以查看線性列表,經典類沒有這個屬性 #新式類繼承順序:F->D->B->E->C->A #經典類繼承順序:F->D->B->A->E->C #python3中統一都是新式類 #pyhon2中才分新式類與經典類
1.6.2 繼承原理
python到底是如何實現繼承的,對於你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如:
>>> F.mro() #等同於F.__mro__ [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
為了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。
而這個MRO列表的構造是通過一個C3線性化演算法來實現的。我們不去深究這個演算法的數學原理,它實際上就是合併所有父類的MRO列表並遵循如下三條準則:
1.子類會先於父類被檢查。
2.多個父類會根據它們在列表中的順序被檢查。
3.如果對下一個類存在兩個合法的選擇,選擇第一個父類。