繼承
什么是繼承
繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為 基類 或 超類 ,新建的類稱為 派生類 或 子類
python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類
pass
class ParentClass2: #定義父類
pass
class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類
pass
提示:如果沒有指定基類,python的類會默認繼承object類,object是所有python類的基類,它提供了一些常見方法(如
__str__
)的實現
>>> ParentClass1.__bases__
(
,)
>>> ParentClass2.__bases__
(
,)
抽象與繼承(先抽象再繼承)
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:
- 將奧巴馬和梅西這倆對象比較像的部分抽取成類;
- 將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關注點,降低復雜度)
繼承:是基于抽象的結果,通過編程語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構
抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類
繼承與重用性
在開發程序的過程中,如果我們定義了一個類A,然后又想新建立另外一個類B,但是類B的大部分內容與類A的相同時
我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。
通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數據屬性和函數屬性),實現代碼重用
例子:人狗大戰:
class Animal: # 父類
def __init__(self,hp,ad,name):
self.hp = hp
self.ad = ad
self.name = name
def eat(self):
print('%s執行我啦'%self.name)
self.hp += 5
class Person(Animal):
def __init__(self,name,hp,ad,sex,job):
self.sex = sex # 人特有的
self.job = job # 人特有的
Animal.__init__(self, hp, ad, name)
def attack(self,dog):
dog.hp -= self.ad
print('%s攻擊了%s,%s掉了%s點血'%(self.name,dog.name,dog.name,self.ad))
class Dog(Animal):
def __init__(self,name,kind,hp,ad): # 初始化方法
Animal.__init__(self,hp,ad,name) # 將父類的
self.kind = kind # kind是狗特有的
def bite(self,person):
person.hp -= self.ad
print('%s咬了%s,%s掉了%s點血'%(self.name,person.name,person.name,self.ad))
二餅 = Dog('二餅','哈士奇',3000,150)
print(二餅.__dict__)
alex = Person('alex',100,5,'不詳','乞丐')
print(alex.__dict__)
提示:用已經有的類建立一個新的類,這樣就重用了已經有的軟件中的一部分設置大部分,大大生了編程工作量,這就是常說的軟件重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定制新的數據類型,這樣就是大大縮短了軟件開發周期,對大型軟件開發來說,意義重大.
# A和B類都需要調用相同的方法
# 創建父類C,把相同的方法放到C類中
# A和B繼承C A(C) B(C)
# A的對象和B的對象就可以直接調用C中的方法了
# A和B中有相同的方法,一部分功能相同,還有不同的部分
# 創建父類C, 把相同的部分放到C類的方法中
# 在A\B中保留不同的部分,
# 然后分別在A\B中調用C類的方法即可
通過繼承建立了派生類與基類之間的關系,它是一種'是'的關系,比如白馬是馬,人是動物。
當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如教授是老師
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
抽象類與接口類
接口類
繼承有兩種用途:
一:繼承基類的方法,并且做出自己的改變或者擴展(代碼重用)
二:聲明某個子類兼容于某基類,定義一個接口類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)
接口初成:手動報異常:
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的關鍵字,上面的代碼只是看起來像接口,其實并沒有起到接口的作用,子類完全可以不用去實現接口 ,如果非要去模仿接口的概念,可以借助第三方模塊:
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一樣,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)
抽象類與接口類
抽象類的本質還是類,指一組類的相似性,包括數據屬性和函數屬性,而接口類強調只強調函數屬性的相似性
抽象類是一個介于接口和類之間的一個概念,同時具備類和接口的部分特性,可以用來實現歸一化設計
在python并沒有接口類這種東西,即使不通過專門的模塊定義,也應該有一些概念
多繼承問題
在繼承抽象類的時候,盡量避免多繼承
而在接口接口的時候,推薦多使用多繼承
接口隔離原則:使用多個專門的接口,而不使用單一的總接口,既客戶端不需要依賴的那些不需要接口
方法的實現
在抽象類中,我們可以對一些抽象方法,做出基本實現
而在接口類中,任何方法都只是一種規范,具體的功能需要子類的實現
super語法
super()
函數是派生類用于繼承父類(超類)的一個方法
super
是用于解決多繼承問題的,直接使用類名調用父類的方法在使用單繼承時沒問題,但如果涉及多繼承,會出現問題,因為多繼承會涉及到繼承順序(MRO),重復調用(鉆石繼承)等種種問題
MRO 就是類的方法解析順序表,是一個列表,也是繼承父類方法時的順序表
-
語法
super().__init__(name) # 第一種方法 super(Student, self).__init__(name) # 第二種方法
-
實例
class Course: course_lst = [] def __init__(self,name,period,price): self.name = name self.period = period self.price = price class Role: def __init__(self,name): self.name = name def show_course(self): for item in Course.course_lst: print(item.name,item.period,item.price) class Student(Role): def __init__(self,name): # Role.__init__(self,name) # super(Student, self).__init__(name) super().__init__(name) # super也可以幫助我們找到父類 self.courses = [] class Manager(Role):pass python = Course('python','6 months',19800) linux = Course('linux','5 months',17800) Course.course_lst = [python,linux] # 所有的可選課程 m = Student('alex') print(m.name) m.show_course()
鉆石繼承 && 多繼承
繼承順序
class A:
pass
def func(self):
print('in A')
class C(A):
pass
# def func(self):
# print('in C')
class B(A):
pass
# def func(self):
# print('in B')
class D(B):
pass
# def func(self):
# print('in D')
class E(C):
pass
# def func(self):
# print('in E')
class F(D,E):
pass
# def func(self):
# print('in F')
# f = F()
# f.func()
print(F.mro()) # 查看多繼承中的繼承順序
# 順序 遵循C3算法
#重新認識super
class D:
def func(self):print('D')
class C(D):
def func(self):
super().func()
print('C')
class B(D):
def func(self):
super().func()
print('B')
class A(B,C):
def func(self):
super().func()
print('A')
a = A()
a.func()
b = B()
b.func()
#新式類繼承順序:F->D->B->E->C->A
#經典類繼承順序:F->D->B->A->E->C
#python3中統一都是新式類
#pyhon2中才分新式類與經典類
繼承原理
python到底是如何實現繼承的,對于你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如
>>> F.mro() #等同于F.__mro__
[
,
,
,
,
,
,
]
為了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。
而這個MRO列表的構造是通過一個C3線性化算法來實現的。我們不去深究這個算法的數學原理,它實際上就是合并所有父類的MRO列表并遵循如下三條準則:
1.子類會先于父類被檢查
2.多個父類會根據它們在列表中的順序被檢查
3.如果對下一個類存在兩個合法的選擇,選擇第一個父類
繼承小結
繼承的作用
減少代碼的重用
提高代碼可讀性
規范編程模式
幾個名詞
抽象:抽象即抽取類似或者說比較像的部分。是一個從具題到抽象的過程。
繼承:子類繼承了父類的方法和屬性
派生:子類在父類方法和屬性的基礎上產生了新的方法和屬性
抽象類與接口類
1.多繼承問題
在繼承抽象類的過程中,我們應該盡量避免多繼承;
而在繼承接口的時候,我們反而鼓勵你來多繼承接口
2.方法的實現
在抽象類中,我們可以對一些抽象方法做出基礎實現;
而在接口類中,任何方法都只是一種規范,具體的功能需要子類實現
鉆石繼承
新式類:廣度優先
經典類:深度優先
object類
object是python的默認類,有很多的方法,python種默認的list,str,dict等等都是繼承了object類的方法
繼承了object的類屬于新式類 ,沒有繼承屬于經典類
在python3種默認都是新式類,也即是所有的自定義類,基類都會繼承object類
描述
在python3.x的所有類都是object的子類
所以對于一些內置的方法會寫在object類中
如果子類不定義,在調用的時候最終會調用object類中的方法
就不會讓程序出現不必要的錯誤了
__init__方法就是其中的一個例子
所有繼承了object類的類 ---- 新式類
在python2中 不繼承object類的都是 經典類
格式
class A(object):
pass # 新式類
class A:
pass # 經典類 :在多繼承中遵循深度優先
# 經典類中沒有super和mro方法
總結
所有的py3中 的類都繼承object 是新式類
在繼承中 遵循 廣度優先的 C3算法
也可以使用mro來查看繼承順序
super這個方法 可以幫助我們查找到mro順序中的下一個類
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
