一、繼承 1. 概念 繼承是一種創建新類的方式,新建的類可以繼承一個或多個父類(python支持多繼承),父類又可稱為基類或超類,新建的類稱為派生類或子類。 子類會“”遺傳”父類的屬性,從而解決代碼重用問題。 查看繼承 在開發程式的過程中,如果我們定義了一個類A,然後又想新建立另外一個類B,但是類B ...
一、繼承
1. 概念
繼承是一種創建新類的方式,新建的類可以繼承一個或多個父類(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'>)
issubclass(sub, super)檢查sub類是否是 super 類的子類
class Foo(object): pass class Bar(Foo): pass issubclass(Bar, Foo)
在開發程式的過程中,如果我們定義了一個類A,然後又想新建立另外一個類B,但是類B的大部分內容與類A的相同時
我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。
通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數據屬性和函數屬性),實現代碼重用
==========================第一部分 例如 貓可以:喵喵叫、吃、喝、拉、撒 狗可以:汪汪叫、吃、喝、拉、撒 如果我們要分別為貓和狗創建一個類,那麼就需要為 貓 和 狗 實現他們所有的功能,偽代碼如下: #貓和狗有大量相同的內容 class 貓: def 喵喵叫(self): print '喵喵叫' def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something class 狗: def 汪汪叫(self): print '喵喵叫' def 吃(self): # do something 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 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) def shit(self): print ("%s 拉 " %self.name) def pee(self): print ("%s 撒 " %self.name) class Cat(Animal): def __init__(self, name): self.name = name self.breed = '貓' def cry(self): print('喵喵叫') class Dog(Animal): def __init__(self, name): self.name = name self.breed='狗' def cry(self): print('汪汪叫') # ######### 執行 ######### c1 = Cat('小白家的小黑貓') c1.eat() c2 = Cat('小黑的小白貓') c2.drink() d1 = Dog('胖子家的小瘦狗') d1.eat() 使用繼承來重用代碼比較好的例子
2. 多繼承
- Python的類可以繼承多個類,Java和C#中則只能繼承一個類
- Python的類如果繼承了多個類,那麼其尋找方法的方式有兩種,分別是:深度優先和廣度優先
- 當類是經典類時,多繼承情況下,會按照深度優先方式查找
- 當類是新式類時,多繼承情況下,會按照廣度優先方式查找
經典類和新式類,從字面上可以看出一個老一個新,新的必然包含了跟多的功能,也是之後推薦的寫法,從寫法上區分的話,如果 當前類或者父類繼承了object類,那麼該類便是新式類,否則便是經典類。
1.只有在python2中才分新式類和經典類,python3中統一都是新式類
2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類
3.在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類
4.在python3中,無論是否繼承object,都預設繼承object,即python3中所有類均為新式類
1 class D: 2 def bar(self): 3 print 'D.bar' 4 5 class C(D): 6 def bar(self): 7 print 'C.bar' 8 9 class B(D): 10 def bar(self): 11 print 'B.bar' 12 13 class A(B, C): 14 def bar(self): 15 print 'A.bar' 16 17 a = A() 18 # 執行bar方法時 19 # 首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中麽有,則繼續去D類中找,如果D類中麽有,則繼續去C類中找,如果還是未找到,則報錯 20 # 所以,查找順序:A --> B --> D --> C 21 # 在上述查找bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了 22 a.bar()經典類多繼承
1 class D(object): 2 def bar(self): 3 print 'D.bar' 4 5 class C(D): 6 def bar(self): 7 print 'C.bar' 8 9 class B(D): 10 def bar(self): 11 print 'B.bar' 12 13 class A(B, C): 14 def bar(self): 15 print 'A.bar' 16 17 a = A() 18 # 執行bar方法時 19 # 首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中麽有,則繼續去C類中找,如果C類中麽有,則繼續去D類中找,如果還是未找到,則報錯 20 # 所以,查找順序:A --> B --> C --> D 21 # 在上述查找bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了 22 a.bar()新式類多繼承
3. 繼承原理
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.如果對下一個類存在兩個合法的選擇,選擇第一個父類
4. 在子類中調用父類的方法
- 指明道姓
class Dad(object): money = 100000000 def __init__(self, name, age): self.name = name self.age = age def hit_son(self): print('%s 正在打兒子' % self.name) class Son(Dad): def __init__(self, name, age): Dad.__init__(self, name, age)
- super
class Dad(object): money = 100000000 def __init__(self, name, age): self.name = name self.age = age def hit_son(self): print('%s 正在打兒子' % self.name) class Son(Dad): def __init__(self, name, age): super().__init__(name, age)
5. 介面與歸一化設計
1 =================第一部分:Java 語言中的介面很好的展現了介面的含義: IAnimal.java 2 /* 3 * Java的Interface介面的特征: 4 * 1)是一組功能的集合,而不是一個功能 5 * 2)介面的功能用於交互,所有的功能都是public,即別的對象可操作 6 * 3)介面只定義函數,但不涉及函數實現 7 * 4)這些功能是相關的,都是動物相關的功能,但光合作用就不適宜放到IAnimal裡面了 */ 8 9 package com.oo.demo; 10 public interface IAnimal { 11 public void eat(); 12 public void run(); 13 public void sleep(); 14 public void speak(); 15 } 16 17 =================第二部分:Pig.java:豬”的類設計,實現了IAnnimal介面 18 package com.oo.demo; 19 public class Pig implements IAnimal{ //如下每個函數都需要詳細實現 20 public void eat(){ 21 System.out.println("Pig like to eat grass"); 22 } 23 24 public void run(){ 25 System.out.println("Pig run: front legs, back legs"); 26 } 27 28 public void sleep(){ 29 System.out.println("Pig sleep 16 hours every day"); 30 } 31 32 public void speak(){ 33 System.out.println("Pig can not speak"); } 34 } 35 36 =================第三部分:Person2.java 37 /* 38 *實現了IAnimal的“人”,有幾點說明一下: 39 * 1)同樣都實現了IAnimal的介面,但“人”和“豬”的實現不一樣,為了避免太多代碼導致影響閱讀,這裡的代碼簡化成一行,但輸出的內容不一樣,實際項目中同一介面的同一功能點,不同的類實現完全不一樣 40 * 2)這裡同樣是“人”這個類,但和前面介紹類時給的類“Person”完全不一樣,這是因為同樣的邏輯概念,在不同的應用場景下,具備的屬性和功能是完全不一樣的 */ 41 42 package com.oo.demo; 43 public class Person2 implements IAnimal { 44 public void eat(){ 45 System.out.println("Person like to eat meat"); 46 } 47 48 public void run(){ 49 System.out.println("Person run: left leg, right leg"); 50 } 51 52 public void sleep(){ 53 System.out.println("Person sleep 8 hours every dat"); 54 } 55 56 public void speak(){ 57 System.out.println("Hellow world, I am a person"); 58 } 59 } 60 61 =================第四部分:Tester03.java 62 package com.oo.demo; 63 64 public class Tester03 { 65 public static void main(String[] args) { 66 System.out.println("===This is a person==="); 67 IAnimal person = new Person2(); 68 person.eat(); 69 person.run(); 70 person.sleep(); 71 person.speak(); 72 73 System.out.println("\n===This is a pig==="); 74 IAnimal pig = new Pig(); 75 pig.eat(); 76 pig.run(); 77 pig.sleep(); 78 pig.speak(); 79 } 80 }java的介面
介面提取了一群類共同的函數,可以把介面當做一個函數的集合。
然後讓子類去實現介面中的函數。
這麼做的意義在於歸一化,什麼叫歸一化,就是只要是基於同一個介面實現的類,那麼所有的這些類產生的對象在使用時,從用法上來說都一樣。
歸一化的好處在於:
1. 歸一化讓使用者無需關心對象的類是什麼,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
2. 歸一化使得高層的外部使用者可以不加區分的處理所有介面相容的對象集合
2.1:就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是記憶體、磁碟、網路還是屏幕(當然,對底層設計者,當然也可以區分出“字元設備”和“塊設備”,然後做出針對性的設計:細緻到什麼程度,視需求而定)。
2.2:再比如:我們有一個汽車介面,裡面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現了汽車介面,這樣就好辦了,大家只需要學會了怎麼開汽車,那麼無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函數調用)都一樣
在python中根本就沒有一個叫做interface的關鍵字,如果非要去模仿介面的概念可以藉助第三方模塊:
http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py里使用zope.interface
文檔https://zopeinterface.readthedocs.io/en/latest/
設計模式:https://github.com/faif/python-patterns
也可以使用繼承:
繼承的兩種用途
一:繼承基類的方法,並且做出自己的改變或者擴展(代碼重用):實踐中,繼承的這種用途意義並不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。
二:聲明某個子類相容於某基類,定義一個介面類(模仿java的Interface),介面類中定義了一些介面名(就是函數名)且並未實現介面的功能,子類繼承介面類,並且實現介面中的功能
1 class Interface:#定義介面Interface類來模仿介面的概念,python中壓根就沒有interface關鍵字來定義一個介面。 2 def read(self): #定介面函數read 3 pass 4 5 def write(self): #定義介面函數write 6 pass 7 8 9 class Txt(Interface): #文本,具體實現read和write 10 def read(self): 11 print('文本數據的讀取方法') 12 13 def write(self): 14 print('文本數據的讀取方法') 15 16 class Sata(Interface): #磁碟,具體實現read和write 17 def read(self): 18 print('硬碟數據的讀取方法') 19 20 def write(self): 21 print('硬碟數據的讀取方法') 22 23 class Process(Interface): 24 def read(self): 25 print('進程數據的讀取方法') 26 27 def write(self): 28 print('進程數據的讀取方法')View Code
上面的代碼只是看起來像介面,其實並沒有起到介面的作用,子類完全可以不用去實現介面 ,這就用到了抽象類
1 什麼是抽象類
與java一樣,python也有抽象類的概念但是同樣需要藉助模塊實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化
2 為什麼要有抽象類
如果說類是從一堆對象中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括數據屬性和函數屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。
從設計角度去看,如果類是從現實對象抽象而來的,那麼抽象類就是基於類抽象而來的。
從實現角度來看,抽象類與普通類的不同之處在於:抽象類中只能有抽象方法(沒有實現功能),該類不能被實例化,只能被繼承,且子類必須實現抽象方法。這一點與介面有點類似,但其實是不同的,即將揭曉答案
#一切皆文件 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)
二、多態
多態指的是一類事物有多種形態
動物有多種形態:人,狗,豬
import abc class Animal(metaclass=abc.ABCMeta): #同一類事物:動物 @abc.abstractmethod def talk(self): pass class People(Animal): #動物的形態之一:人 def talk(self): print('say hello') class Dog(Animal): #動物的形態之二:狗 def talk(self): print('say wangwang') class Pig(Animal): #動物的形態之三:豬 def talk(self): print('say aoao')
三、封裝
1. 隱藏
在python中用雙下劃線開頭的方式將屬性隱藏起來(設置成私有的)
#其實這僅僅這是一種變形操作且僅僅只在類定義階段發生變形 #類中所有雙下劃線開頭的名稱如__x都會在類定義時自動變形成:_類名__x的形式: class A: __N=0 #類的數據屬性就應該是共用的,但是語法上是可以把類的數據屬性設置成私有的如__N,會變形為_A__N def __init__(self): self.__X=10 #變形為self._A__X def __foo(self): #變形為_A__foo print('from A') def bar(self): self.__foo() #只有在類內部才可以通過__foo的形式訪問到. #A._A__N是可以訪問到的, #這種,在外部是無法通過__x這個名字訪問到。
2. 封裝
封裝的真諦在於明確地區分內外,封裝的屬性可以直接在內部使用,而不能被外部直接使用,然而定義屬性的目的終歸是要用,外部要想用類隱藏的屬性,需要我們為其開闢介面,讓外部能夠間接地用到我們隱藏起來的屬性,那這麼做的意義何在???
1:封裝數據:將數據隱藏起來這不是目的。隱藏起來然後對外提供操作該數據的介面,然後我們可以在介面附加上對該數據操作的限制,以此完成對數據屬性操作的嚴格控制。
class Teacher: def __init__(self,name,age): # self.__name=name # self.__age=age self.set_info(name,age) def tell_info(self): print('姓名:%s,年齡:%s' %(self.__name,self.__age)) def set_info(self,name,age): if not isinstance(name,str): raise TypeError('姓名必須是字元串類型') if not isinstance(age,int): raise TypeError('年齡必須是整型') self.__name=name self.__age=age t=Teacher('egon',18) t.tell_info() t.set_info('egon',19) t.tell_info()
2:封裝方法:目的是隔離複雜度
#取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、列印賬單、取錢 #對使用者來說,只需要知道取款這個功能即可,其餘功能我們都可以隱藏起來,很明顯這麼做 #隔離了複雜度,同時也提升了安全性 class ATM: def __card(self): print('插卡') def __auth(self): print('用戶認證') def __input(self): print('輸入取款金額') def __print_bill(self): print('列印賬單') def __take_money(self): print('取款') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw() 隔離複雜度的例子
3. 特性(propety)
property是一種特殊的屬性,訪問它時會執行一段功能(函數)然後返回值
import math class Circle: def __init__(self,radius): #圓的半徑radius self.radius=radius @property def area(self): return math.pi * self.radius**2 #計算面積 @property def perimeter(self): return 2*math.pi*self.radius #計算周長 c=Circle(10) print(c.radius) print(c.area) #可以向訪問數據屬性一樣去訪問area,會觸發一個函數的執行,動態計算出一個值 print(c.perimeter) #同上 ''' 輸出結果: 314.1592653589793 62.83185307179586 ''' #註意:此時的特性arear和perimeter不能被賦值 c.area=3 #為特性area賦值 ''' 拋出異常: AttributeError: can't set attribute '''
class Foo: def __init__(self,val): self.__NAME=val #將所有的數據屬性都隱藏起來 @property def name(self): return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置) @name.setter def name(self,value): if not isinstance(value,str): #在設定值之前進行類型檢查 raise TypeError('%s must be str' %value) self.__NAME=value #通過類型檢查後,將值value存放到真實的位置self.__NAME @name.deleter def name(self): raise TypeError('Can not delete') f=Foo('egon') print(f.name) # f.name=10 #拋出異常'TypeError: 10 must be str' del f.name #拋出異常'TypeError: Can not delete'