Day29:類的繼承、派生、組合和接口
一、類的繼承、派生和組合
繼承是一種創建新類的方式,在Python中,新類可以繼承一個或多個父類,父類又可稱為基類或者超類,新建的類稱為派生類或子類。
在Python3中,所有類都默認繼承object,都是新式類。
在Python2中,有經典類和新式類。
沒有繼承object類以及object的子類的類都是經典類。
1、繼承
Python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類 pass class ParentClass2: #定義父類 pass class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass1 pass class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類 pass
查看繼承
使用<類名.__base__>查看類的第一個父類
使用<類名.__bases__>查看類的父類
>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個父類 (<class ‘__main__.ParentClass1‘>,) >>> SubClass2.__bases__ #__bases__則是查看所有繼承的父類 (<class ‘__main__.ParentClass1‘>, <class ‘__main__.ParentClass2‘>)
提示:如果沒有指定基類,python的類會默認繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現。
>>> ParentClass1.__bases__ (<class ‘object‘>,) >>> ParentClass2.__bases__ (<class ‘object‘>,)
class People: pass class Animal: pass class Student(People,Animal): pass print(Student.__bases__) #(<class ‘__main__.People‘>, <class ‘__main__.Animal‘>) print(People.__bases__) #(<class ‘object‘>,) print(Animal.__bases__) #(<class ‘object‘>,) print(Student.__base__) #<class ‘__main__.People‘>
繼承也是為了解決代碼重復的問題,減少代碼冗余。
繼承是用來創建新的類的一種方式。
繼承是一種什麽“是”什麽的關系。
2、繼承與抽象(先抽象再繼承)
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:
1.將奧巴馬和梅西這倆對象比較像的部分抽取成類;
2.將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關註點,降低復雜度)
繼承:是基於抽象的結果,通過編程語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類。
3、繼承與重用性
在開發程序的過程中,如果我們定義了一個類A,然後又想新建立另外一個類B,但是類B的大部分內容與類A的相同時,我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。
通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數據屬性和函數屬性),實現代碼重用。
class Hero: def __init__(self,nickname,aggressivity,life_value): self.nickname=nickname self.aggressivity=aggressivity self.life_value=life_value def attack(self,enemy): enemy.life_value-=self.aggressivity class Garen(Hero): pass class Riven(Hero): pass g1=Garen(‘草叢倫‘,100,300) r1=Riven(‘銳雯雯‘,57,200) print(g1.life_value) r1.attack(g1) print(g1.life_value) ‘‘‘ 運行結果 243 ‘‘‘
提示:用已經有的類建立一個新的類,這樣就重用了已經有的軟件中的一部分設置大部分,大大省了編程工作量,這就是常說的軟件重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定制新的數據類型,這樣就是大大縮短了軟件開發周期,對大型軟件開發來說,意義重大.。
註意:像g1.life_value之類的屬性引用,會先從實例中找life_value然後去類中找,然後再去父類中找...直到最頂級的父類。
4、派生
當然子類也可以添加自己新的屬性或者在自己這裏重新定義這些屬性(不會影響到父類),需要註意的是,一旦重新定義了自己的屬性且與父類重名,那麽調用新增的屬性時,就以自己為準了。這就是類的派生。
class Riven(Hero): camp=‘Noxus‘ def attack(self,enemy): #在自己這裏定義新的attack,不再使用父類的attack,且不會影響父類 print(‘from riven‘) def fly(self): #在自己這裏定義新的 print(‘%s is flying‘ %self.nickname)
在子類中,新建的重名的函數屬性,在編輯函數內功能的時候,有可能需要重用父類中重名的那個函數功能,應該是用調用普通函數的方式,即:類名.func(),此時就與調用普通函數無異了,因此即便是self參數也要為其傳值。
class Riven(Hero): camp=‘Noxus‘ def __init__(self,nickname,aggressivity,life_value,skin): Hero.__init__(self,nickname,aggressivity,life_value) #調用父類功能 self.skin=skin #新屬性 def attack(self,enemy): #在自己這裏定義新的attack,不再使用父類的attack,且不會影響父類 Hero.attack(self,enemy) #調用功能 print(‘from riven‘) def fly(self): #在自己這裏定義新的 print(‘%s is flying‘ %self.nickname) r1=Riven(‘銳雯雯‘,57,200,‘比基尼‘) r1.fly() print(r1.skin) ‘‘‘ 運行結果 銳雯雯 is flying 比基尼 ‘‘‘
class People: #定義父類 def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def walk(self): print(‘%s is walking‘ %self.name) def foo(self): print(‘from father %s‘ %self.name) class Teacher(People): #繼承父類 def __init__(self,name,age,sex,level,salary): #新建__init__方法 People.__init__(self,name,age,sex) #還使用父類的方法 self.level = level #新增的內容 self.salary = salary def tech(self): #子類特有的函數屬性 print(‘%s is teaching ‘ %self.name) def foo(self): People.foo(self) print(‘from Teacher‘) #對父類的函數屬性引用和新增 class Student(People): def __init__(self,name,age,sex,group): People.__init__(self,name,age,sex) self.group = group def study(self): print(‘%s is studying‘%self.name) t=Teacher(‘egon‘,18,‘male‘,10,3000) print(t.salary) t.tech() t.foo() ‘‘‘ 3000 egon is teaching from father egon from Teacher ‘‘‘
5、組合與與重用性
軟件重用的重要方式除了繼承之外還有一種方式,即:組合。
組合指的是,在一個類中以另外一個類的對象作為數據屬性,稱為類的組合。
class Date: #定義一個關於日期的類 def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day def tell_birth(self): print(‘出生於%s年 %s月 %s日‘%(self.year,self.mon,self.day)) class Teacher: #定義一個老師的類 def __init__(self,name,age,year,mon,day): self.name = name self.age = age self.birth = Date(year,mon,day) #使用Date的類 def teach(self): print(‘%s is teaching‘%self.name) t=Teacher(‘egon‘,18,1990,12,11) t.birth.tell_birth() ‘‘‘ 出生於1990年 12月 11日 ‘‘‘
組合與繼承都是有效地利用已有類的資源的重要方式。但是二者的概念和使用場景皆不同。
5.1 繼承的方式
通過繼承建立了派生類與基類之間的關系,它是一種‘是‘的關系,比如白馬是馬,人是動物。
當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如教授是老師。
>>> 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
5.2 組合的方式
用組合的方式建立了類與組合的類之間的關系,它是一種‘有’的關系,比如教授有生日,教授教python課程。
class BirthDate: def __init__(self,year,month,day): self.year=year self.month=month self.day=day class Couse: def __init__(self,name,price,period): self.name=name self.price=price self.period=period class Teacher: def __init__(self,name,gender): self.name=name self.gender=gender def teach(self): print(‘teaching‘) class Professor(Teacher): def __init__(self,name,gender,birth,course): Teacher.__init__(self,name,gender) self.birth=birth self.course=course p1=Professor(‘egon‘,‘male‘, BirthDate(‘1995‘,‘1‘,‘27‘), Couse(‘python‘,‘28000‘,‘4 months‘)) print(p1.birth.year,p1.birth.month,p1.birth.day) print(p1.course.name,p1.course.price,p1.course.period) ‘‘‘ 運行結果: 1 27 python 28000 4 months ‘‘‘
二、接口與歸一化設計
1、什麽是接口
繼承有兩種用途:
一:繼承基類的方法,並且做出自己的改變或者擴展(代碼重用)。
二:聲明某個子類兼容於某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,並且實現接口中的功能。
class File: #定義接口File類來模仿接口的概念。 def read(self): #定接口函數read pass def write(self): #定義接口函數write pass class Txt(File): #文本,具體實現read和write def read(self): print(‘文本數據的讀取方法‘) def write(self): print(‘文本數據的讀取方法‘) class Sata(file): #磁盤,具體實現read和write def read(self): print(‘硬盤數據的讀取方法‘) def write(self): print(‘硬盤數據的讀取方法‘) class Process(File): def read(self): print(‘進程數據的讀取方法‘) def write(self): print(‘進程數據的讀取方法‘)
#父類要限制 #1:子類必須要有父類的方法 #2:子類實現的方法必須跟父類的方法的名字一樣 import abc class File(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass class Txt(File): #文本,具體實現read和write def read(self): pass def write(self): pass t=Txt()
實踐中,繼承的第一種含義意義並不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。
繼承的第二種含義非常重要。它又叫“接口繼承”。
接口繼承實質上是要求“做出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的所有對象”——這在程序設計上,叫做歸一化。
歸一化使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合——就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存、磁盤、網絡還是屏幕(當然,對底層設計者,當然也可以區分出“字符設備”和“塊設備”,然後做出針對性的設計:細致到什麽程度,視需求而定)。
2、為何要有接口
接口提取了一群類共同的函數,可以把接口當做一個函數的集合。
然後讓子類去實現接口中的函數。
這麽做的意義在於歸一化,什麽叫歸一化,就是只要是基於同一個接口實現的類,那麽所有的這些類產生的對象在使用時,從用法上來說都一樣。
歸一化,讓使用者無需關心對象的類是什麽,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
比如:我們定義一個動物接口,接口裏定義了有跑、吃、呼吸等接口函數,這樣老鼠的類去實現了該接口,松鼠的類也去實現了該接口,由二者分別產生一只老鼠和一只松鼠送到你面前,即便是你分別不到底哪只是什麽鼠你肯定知道他倆都會跑,都會吃,都能呼吸。
再比如:我們有一個汽車接口,裏面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現了汽車接口,這樣就好辦了,大家只需要學會了怎麽開汽車,那麽無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函數調用)都一樣
三、課後作業
定義如下類,並最大程度地重用代碼(使用繼承或組合,派生+子類重用父類方法)
老師類
學生類
分數類
課程類
日期類
1 class Date: #定義日期類 2 def __init__(self,year,mon,day): 3 self.year=year 4 self.mon=mon 5 self.day=day 6 def tell_day(self): 7 print(‘出生日期:%s-%s-%s‘ %(self.year,self.mon,self.day)) 8 class Course: #定義課程類 9 def __init__(self,subjects,hour): 10 self.hour=hour 11 self.subjects=subjects 12 def print_subjects(self): 13 for i in self.subjects: 14 print(i) 15 print(‘共需要%s學時‘%self.hour) 16 class People: 17 def __init__(self, name, age, sex): 18 self.name = name 19 self.age = age 20 self.sex = sex 21 def walk(self): 22 print(‘%s is walking‘ %self.name) 23 def eat(self): 24 print(‘%s is eating‘ %self.name) 25 def tell_birth(self,year,mon,day): 26 self.birth=Date(year,mon,day) 27 self.birth.tell_day() 28 class Teacher(People): 29 def __init__(self,name,age,sex,level,salary): 30 People.__init__(self,name,age,sex) 31 self.level = level 32 self.salary = salary 33 def tech(self,subjects,hour): 34 Course(subjects, hour).print_subjects() 35 class Student(People): 36 def __init__(self,name,age,sex,group): 37 People.__init__(self,name,age,sex) 38 self.group = group 39 def study(self,subjects,hour): 40 Course(subjects,hour).print_subjects() 41 print(‘*‘*20) 42 t=Teacher(‘egon‘,18,‘male‘,100,3000) 43 t.tell_birth(1990,11,11) 44 t.tech([‘python‘],15) 45 print(‘*‘*20) 46 s=Student(‘egon‘,18,‘male‘,‘八組‘) 47 print(s.name,s.age,s.sex,s.group) 48 s.tell_birth(1990,1,1) 49 s.study([‘python‘,‘linux‘],30) 50 ‘‘‘ 51 運行結果 52 ******************** 53 出生日期:1990-11-11 54 python 55 共需要15學時 56 ******************** 57 egon 18 male 八組 58 出生日期:1990-1-1 59 python 60 linux 61 共需要30學時 62 ‘‘‘參考
Day29:類的繼承、派生、組合和接口