继承、派生、组合
繼承
類之間會有一些相同的屬性,提取這些相同的屬性做成基類(父類)
繼承是創建類的一種方式,通過代碼重用,減少代碼冗余。把父類的屬性遺傳給子類。
在創建類時,新建的類可以繼承一個或多個父類,方式如下:
繼承是一種類與類之間的關系: 什么 是 什么 。
查看所有繼承:ClassName.__bases__ ,返回元組。
在python3中,所有類默認繼承object。
在python2中,繼承了object的子類都稱為新式類,而沒有繼承object及其子類的,稱為經典類。
繼承的子類會獲得父類的所有屬性。對象在調用屬性時,尋找順序如下:
對象名稱空間 >>> 所屬類名稱空間 >>> 父類名稱空間
單純的繼承沒有意義,子類需要派生自己新的屬性:
組合
組合也是一種類與類之間的關系: 什么 有 什么 。也是通過代碼重用,減少代碼冗余。
class Date():def __init__(self,year,mon,day):self.year = yearself.mon = monself.day = daydef tell_info(self):print('birth is %s-%s-%s' % (self.year, self.mon, self.day))class Teacher():def __init__(self, name, age, *args): # 這里用*args接收year,mon,dayself.name = nameself.age = ageself.birth = Date(*args) # birth屬性的是Date類的對象。t = Teacher('egon',18,1990,2,30) # 1990,2,30 這三個參數傳給Date中的__init__函數 t.birth.tell_info() ''' birth is 1990-2-30 ''' class People:def __init__(self,name,age,sex): # 函數的默認參數不要寫成可變的,所以couser=[]不要寫在形參位置self.name = nameself.age = ageself.sex = sexself.course = []def tell_info(self):print('''----- %s info -----NAME: %sAGE: %sSEX: %s'''%(self.name,self.name, self.age,self.sex))def tell_course(self):if self.course:for i in self.course:print(i.tell_info())else:print('no course added!')# def tell_couser(self):# if 'couser' in self.__dict__: # 如果不設couser默認參數,那么就沒有couser屬性,# 如果用 if self.couser: 就會報錯。判斷字符串在名稱空間的字典就沒問題# for i in self.couser:# print(i.tell_info)# else:# print('no couser added')class Teacher(People):def __init__(self,name,age,sex,salary,level): # 函數的默認參數不要寫成可變的,所以couser=[]不要寫在形參位置People.__init__(self,name,age,sex)self.salary = salaryself.level = levelclass Student(People):def __init__(self,name,age,sex,group):People.__init__(self,name,age,sex)self.group=groupclass Date:def __init__(self,year,mon,day):self.year = yearself.mon = monself.day = daydef tell_info(self,obj):print('%s 出生于:%s-%s-%s' % (obj.name,self.year, self.mon, self.day))class Course:def __init__(self,name,price,period):self.name = nameself.price = priceself.period = perioddef tell_info(self):print('''----- %s info -----name: %sprice: %speriod: %s'''%(self.name,self.name,self.price,self.period))Birth = Date(1990,2,31) # 創建時間對象 python = Course('python',15800,'6monts') # 創建課程對象python go = Course('go', 10000, '6monts') # 創建課程對象 goalex = Teacher('alex',84,'female',300,1) # 創建老師對象 alex.birth = Birth # 將日期組合進老師對象 alex.course.append(python) # 將課程對象組合進老師對象 alex.course.append(go) alex.birth.tell_info(alex) alex.tell_course() alex.tell_info()ayhan = Student('ayhan',18,'male','group7') ayhan.tell_course()接口與歸一化設計
接口,隱藏具體的實現細節,將使用簡單化。比如,在linux中,一切皆文件,比如文本文件、磁盤文件、進程文件,都有讀寫操作,使用者不需要關心每種文件的讀和寫是如何具體實現的,只需要知道只要是文件,就應該有讀方法和寫方法,調用這兩個方法,就可以完成想要的效果。我們可以通過繼承,來模擬這種情況:
class File(): # 定義文件類來模仿接口的概念def read(self): # 定義接口函數readpassdef write(self): # 定義接口函數writepassclass Txt(File): # 具體實現文本文件的read和writedef read(self):print('文本文件讀方法') # 這里用print來模擬,具體的實現可能是很復雜的。def write(self):print('文本文件讀方法')class Sata(File): # 具體實現磁盤文件的read和writedef read(self):print('磁盤文件讀方法')def write(self):print('磁盤文件寫方法')class Process(File): # 具體實現進程文件的read和writedef read(self):print('進程文件讀方法')def write(self):print('進程文件寫方法')t = Txt() s = Sata() p = Process() # 具體到三個不同的對象,使用者只需要知道它們是文件,是文件就有read和write方法: # t.read() t.read() s.read() s.write() p.read() p.write()上面這個栗子中,定義的File這個父類,只放方法名(read, write),相當于一個模板,具體的功能由子類來實現。
但是,子類在定義時,并不一定要遵循父類中的方法,因此,父類有必要對此加以限制:
1. 子類必須要有父類的方法
2. 子類實現的方法必須跟父類的方法的名字一樣
通過上面這種方式,我們再定義子類時,就必須要有父類的方法,并且方法名一樣,否則就無法實例化對象:
class Txt(File): # 父類是Filedef du(self):print('文本文件讀方法')def xie(self):print('文本文件寫方法')t = Txt() '''雖然上面的Txt類在定義階段沒問題,但是在實例化創建對象時,會提示無法實例化抽象類,t = Txt() TypeError: Can't instantiate abstract class Txt with abstract methods read, write '''這種把所有方法都統一起來的方式,就是歸一化設計,方便使用。
對象的序列化
pickle可以序列化任何python的數據類型
import pickleclass Sample: # 定義類passobj = Sample() # 創建對象with open(file,'wb')as f: # 序列化pickle.dump(obj,f)with open(file, 'rb')as f: # 反序列化obj = pickle.load(f)注意,對象是依賴于類的,如果反序列化出的對象沒有在內存中找到所屬的類,就會報錯。解決這個問題,可以通過導入模塊的方式,將類導入。因為導入會執行模塊的內容,加載到內存。
繼承的實現原理MRO
情況一:
屬性的查找,從左到右,一條條分支的找。這種情況下,經典和新式類尋找都一樣:
DAE > B > C
情況二
新式類:HEB>FC>GDA 最后一個分支時才找到頭。廣度優先。
經典類:一條分支找到頭,再找下一個分支。深度優先。
HEBA > FC > GD
再說self
self就是調用方法的對象本身!無論何時,找方法時先回自己類開始找!
class A(object):def f3(self):print('A.f3')class B(object):def f1(self):print('B.f1')self.f2()class C(B):def f2(self):print('C.f2')self.f3()def f3(self):print('C.f3')class D(C):def f3(self):print('D.f3')obj = D() obj.f1()''' 打印結果 B.f1 C.f2 D.f3 '''mro()繼承屬性的查找順序
print(H.mro()) 查看繼承,只在新式類有這個屬性:
[<class '__main__.H'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.G'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>]
子類調用父類的方法
class People:def __init__(self,name,age,sex):self.name=nameself.age=ageself.sex=sexclass Teacher(People):def __init__(self,name,age,sex,level):# 指明道姓的調用父類的屬性People.__init__(self,name,age,sex,) # 明確寫出父類的名稱,如果父類改了,那么這里也要改。self.level=levelclass Student(People):def __init__(self,name,age,sex,group):# 通過super()調用父類的屬性super().__init__(name,age,sex) # super()自動傳入'Student' 'self'兩個參數,產生一個對象# 對象調用函數是綁定方法,因此__init__()的第一個參數self也不用手動傳入了。self.group=group在python2中使用super() 子類名和self這個兩個參數還是要手動傳,即super(子類名,self)
另外,只有新式類才可以使用super() 函數,因為該函數在尋找繼承屬性時,是根據mro列表。并且是只要找到一個父類的屬性,就會停止。因此,如果子類中要調用多個父類中的同名屬性時,還是要用指名道姓的方式。
總結
- 上一篇: Shell脚本语言常用命令总结~
- 下一篇: yield表达式形式的应用