1. 繼承
1.1 什么是繼承繼承是一種創(chuàng)建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為基類或超類,新建的類稱為派生類或子類。 python中類的繼承分為:單繼承和多繼承。 class ParentClass1: #定義父類 passclass ParentClass2: #定義父類 passclass SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass passclass 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__)的實現(xiàn)。 >>> ParentClass1.__bases__(<class 'object'>,)>>> ParentClass2.__bases__(<class 'object'>,)
1.2 繼承與抽象抽象即抽取類似或者說比較像的部分。 抽象分成兩個層次: 1.將奧巴馬和梅西這倆對象比較像的部分抽取成類; 2.將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關注點,降低復雜度) 繼承:是基于抽象的結果,通過編程語言去實現(xiàn)它,肯定是先python1基礎教程經(jīng)歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。 抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類
1.3 繼承與重用性使用繼承來解決代碼重用的例子: ==========================第一部分例如 貓可以:吃、喝、爬樹 狗可以:吃、喝、看家如果我們要分別為貓和狗創(chuàng)建一個類,那么就需要為 貓 和 狗 實現(xiàn)他們所有的功能,偽代碼如下:#貓和狗有大量相同的內容class 貓: def 吃(self): # do something def 喝(self): # do something def 爬樹(self): # do somethingclass 狗: def 吃(self): # do something def 喝(self): # do something def 看家(self): #do something==========================第二部分上述代碼不難看出,吃、喝是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次。如果使用 繼承 的思想,如下實現(xiàn): 動物:吃、喝 貓:爬樹(貓繼承動物的功能) 狗:看家(狗繼承動物的功能)偽代碼如下:class 動物: def 吃(self): # do something def 喝(self): # do something# 在類后面括號中寫入另外一個類名,表示當前類繼承另外一個類class 貓(動物): def 爬樹(self): print '喵喵叫'# 在類后面括號中寫入另外一個類名,表示當前類繼承另外一個類class 狗(動物): def 看家(self): print '汪汪叫'==========================第三部分#繼承的代碼實現(xiàn)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('汪汪叫')# ######### 執(zhí)行 #########c1 = Cat('小白家的小黑貓')c1.eat()c2 = Cat('小黑的小白貓')c2.drink()d1 = Dog('胖子家的小瘦狗')d1.eat() 在開發(fā)程序的過程中,如果我們定義了一個類A,然后又想新建立另外一個類B,但是類B的大部分內容與類A的相同時。 我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。 通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數(shù)據(jù)屬性和函數(shù)屬性),實現(xiàn)代碼重用。 class Animal: ''' 人和狗都是動物,所以創(chuàng)造一個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): passclass Person(Animal): passegg = Person('egon',10,1000)ha2 = Dog('二愣子',50,1000)egg.eat()ha2.eat() 提示:用已經(jīng)有的類建立一個新的類,這樣就重用了已經(jīng)有的軟件中的一部分設置大部分,大大生了編程工作量,這就是常說的軟件重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定制新的數(shù)據(jù)類型,這樣就是大大縮短了軟件開發(fā)周期,對大型軟件開發(fā)來說,意義重大。
1.4 派生當然子類也可以添加自己新的屬性或者在自己這里重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那么調用新增的屬性時,就以自己為準了。 class Animal: ''' 人和狗都是動物,所以創(chuàng)造一個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.aggressivityclass Person(Animal): ''' 人類,繼承Animal ''' def attack(self, dog): ''' 派生:人有攻擊的技能 :param dog: ''' dog.life_value -= self.aggressivityegg = 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然后去類中找,然后再去父類中找...直到最頂級的父類。 在子類中,新建的重名的函數(shù)屬性,在編輯函數(shù)內功能的時候,有可能需要重用父類中重名的那個函數(shù)功能,應該是用調用普通函數(shù)的方式,即:類名.func(),此時就與調用普通函數(shù)無異了,因此即便是self參數(shù)也要為其傳值。 在python3中,子類執(zhí)行父類的方法也可以直接用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: ''' 人和狗都是動物,所以創(chuàng)造一個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) #執(zhí)行父類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) #執(zhí)行父類的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,接口類中定義了一些接口名(就是函數(shù)名)且并未實現(xiàn)接口的功能,子類繼承接口類,并且實現(xiàn)接口中的功能。 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): ''' 支付函數(shù),總體負責支付 對應支付的對象和要支付的金額 ''' payment.pay(money)p = Alipay()pay(p,200) 開發(fā)中容易出現(xiàn)的問題: 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): ''' 實現(xiàn)了pay的功能,但是名字不一樣 ''' print('微信支付了%s元'%money)def pay(payment,money): ''' 支付函數(shù),總體負責支付 對應支付的對象和要支付的金額 ''' payment.pay(money)p = Wechatpay()pay(p,200) #執(zhí)行會報錯 接口初成:手動報異常:NotImplementedError來解決開發(fā)中遇到的問題。 class Payment: def pay(self): raise NotImplementedErrorclass Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money)p = Wechatpay() #這里不報錯pay(p,200) #這里報錯了 借用abc模塊來實現(xiàn)接口。 from abc import ABCMeta,abstractmethodclass Payment(metaclass=ABCMeta): @abstractmethod def pay(self,money): passclass Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money)p = Wechatpay() #不調就報錯了 實踐中,繼承的第一種含義意義并不很大,甚至常常是有害的。因為它使得子類與基類出現(xiàn)強耦合。 繼承的第二種含義非常重要。它又叫“接口繼承”。 接口繼承實質上是要求“做出一個良好的抽象,這個抽象規(guī)定了一個兼容接口,使得外部調用者無需關心具體細節(jié),可一視同仁的處理實現(xiàn)了特定接口的所有對象”——這在程序設計上,叫做歸一化。 歸一化使得高層的外部使用者可以不加區(qū)分的處理所有接口兼容的對象集合——就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存、磁盤、網(wǎng)絡還是屏幕(當然,對底層設計者,當然也可以區(qū)分出“字符設備”和“塊設備”,然后做出針對性的設計:細致到什么程度,視需求而定)。 依賴倒置原則: 高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該應該依賴細節(jié);細節(jié)應該依賴抽象。換言之,要針對接口編程,而不是針對實現(xiàn)編程。
為何要用接口: 在python中根本就沒有一個叫做interface的關鍵字,上面的代碼只是看起來像接口,其實并沒有起到接口的作用,子類完全可以不用去實現(xiàn)接口 ,如果非要去模仿接口的概念,可以借助第三方模塊。 接口提取了一群類共同的函數(shù),可以把接口當做一個函數(shù)的集合。 然后讓子類去實現(xiàn)接口中的函數(shù)。 這么做的意義在于歸一化,什么叫歸一化,就是只要是基于同一個接口實現(xiàn)的類,那么所有的這些類產(chǎn)生的對象在使用時,從用法上來說都一樣。 歸一化,讓使用者無需關心對象的類是什么,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。 比如:我們定義一個動物接口,接口里定義了有跑、吃、呼吸等接口函數(shù),這樣老鼠的類去實現(xiàn)了該接口,松鼠的類也去實現(xiàn)了該接口,由二者分別產(chǎn)生一只老鼠和一只松鼠送到你面前,即便是你分別不到底哪只是什么鼠你肯定知道他倆都會跑,都會吃,都能呼吸。 再比如:我們有一個汽車接口,里面定義了汽車所有的功能,然后由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現(xiàn)了汽車接口,這樣就好辦了,大家只需要學會了怎么開汽車,那么無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函數(shù)調用)都一樣。
1.5.2 抽象類什么是抽象類? 與java一樣,python也有抽象類的概念但是同樣需要借助模塊實現(xiàn),抽象類是一個特殊的類,它的特殊之處在于只能被繼承,不能被實例化。 為什么要有抽象類? 如果說類是從一堆對象中抽取相同的內容而來的,那么抽象類就是從一堆類中抽取相同的內容而來的,內容包括數(shù)據(jù)屬性和函數(shù)屬性。 比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要么是吃一個具體的香蕉,要么是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。 從設計角度去看,如果類是從現(xiàn)實對象抽象而來的,那么抽象類就是基于類抽象而來的。 從實現(xiàn)角度來看,抽象類與普通類的不同之處在于:抽象類中有抽象方法,該類不能被實例化,只能被繼承,且子類必須實現(xiàn)抽象方法。這一點與接口有點類似,但其實是不同的,即將揭曉答案。 在python中實現(xiàn)抽象類: #一切皆文件import abc #利用abc模塊實現(xiàn)抽象類class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定義抽象方法,無需實現(xiàn)功能 def read(self): '子類必須定義讀功能' pass @abc.abstractmethod #定義抽象方法,無需實現(xiàn)功能 def write(self): '子類必須定義寫功能' pass# class Txt(All_file):# pass## t1=Txt() #報錯,子類沒有定義抽象方法class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('文本數(shù)據(jù)的讀取方法') def write(self): print('文本數(shù)據(jù)的讀取方法')class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('硬盤數(shù)據(jù)的讀取方法') def write(self): print('硬盤數(shù)據(jù)的讀取方法')class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法 def read(self): print('進程數(shù)據(jù)的讀取方法') def write(self): print('進程數(shù)據(jù)的讀取方法')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 抽象類與接口類抽象類的本質還是類,指的是一組類的相似性,包括數(shù)據(jù)屬性(如all_type)和函數(shù)屬性(如read、write),而接口只強調函數(shù)屬性的相似性。 抽象類是一個介于類和接口直接的一個概念,同時具備類和接口的部分特性,可以用來實現(xiàn)歸一化設計。 在python中,并沒有接口類這種東西,即便不通過專門的模塊定義接口,我們也應該有一些基本的概念。 1.多繼承問題在繼承抽象類的過程中,我們應該盡量避免多繼承; 而在繼承接口的時候,我們反而鼓勵你來多繼承接口。 接口隔離原則: 使用多個專門的接口,而不使用單一的總接口。即客戶端不應該依賴那些不需要的接口。
2.方法的實現(xiàn)在抽象類中,我們可以對一些抽象方法做出基礎實現(xiàn); 而在接口類中,任何方法都只是一種規(guī)范,具體的功能需要子類實現(xiàn)。
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') passf1=F()f1.test()print(F.__mro__) #只有新式才有這個屬性可以查看線性列表,經(jīng)典類沒有這個屬性#新式類繼承順序:F->D->B->E->C->A#經(jīng)典類繼承順序:F->D->B->A->E->C#python3中統(tǒng)一都是新式類#pyhon2中才分新式類與經(jīng)典類
1.6.2 繼承原理python到底是如何實現(xiàn)繼承的,對于你定義的每一個類,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'>] 為了實現(xiàn)繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。 而這個MRO列表的構造是通過一個C3線性化算法來實現(xiàn)的。我們不去深究這個算法的數(shù)學原理,它實際上就是合并所有父類的MRO列表并遵循如下三條準則: 1.子類會先于父類被檢查。 2.多個父類會根據(jù)它們在列表中的順序被檢查。 3.如果對下一個類存在兩個合法的選擇,選擇第一個父類。
|