一、繼承的概念算法
繼承是一種新建類的方式,新建類能夠建立一個或多個父類,父類又能夠稱之爲基類或超類,新建的類稱之爲子類或派生類,繼承描述的是一種遺傳關係,子類能夠重用父類的屬性和方法。使用繼承能夠減小類與類之間代碼冗餘的問題。編程
在 Python 中類的繼承分爲單繼承和多繼承編程語言
class ParentClass1(): # 定義父類
pass
class ParentClass2(): # 定義父類
pass
class SubClass1(ParentClass1): # 定義子類繼承父類, 單繼承
pass
class SubClass2(ParentClass1, ParentClass2): # Python支持多繼承, 用逗號分隔開多個繼承的類
pass
二、查看全部繼承的父類:__bases__ide
class ParentClass1(): pass
class ParentClass2(): pass
class SubClass1(ParentClass1): pass
class SubClass2(ParentClass1, ParentClass2): pass
print(SubClass1.__bases__) print(SubClass2.__bases__) print(ParentClass1.__bases__) print(ParentClass2.__bases__) # 運行結果
(<class '__main__.ParentClass1'>,) (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>) (<class 'object'>,) (<class 'object'>,)
若是沒有指定父類,Python3 會默認繼承 object 類,object 是全部類的父類函數
新式類:但凡繼承 object 類的子類,以及該子類的子子類....,都稱之爲新式類spa
經典類:沒有繼承 object 類的子類,以及該子類的子子類....,都稱之爲經典類code
只有在 Python2 中才區分新式類和經典類,Python3 所有默認是新式類對象
三、繼承與抽象(先抽象後繼承)blog
抽象:抽取類似的部分(也就是提取一類事物的特色,範圍愈來愈大,共性愈來愈少),是從大範圍到小範圍的過程繼承
繼承:是基於抽象的過程,經過編程語言去實現它,確定是先經歷抽象這個過程,才能經過繼承的方式去表達出抽象的結構,是從小範圍到大範圍的過程
四、派生
1)在父類的基礎上產生子類,產生的子類就叫作派生類
2)父類中沒有的方法,在子類中有,這樣的方法就叫作派生方法
3)父類中有個方法,子類中也有這個方法,就叫作方法的重寫(就是把父類裏的方法重寫了)
五、注意的幾個概念
1)子類可使用父類的全部屬性和方法
2)若是子類有本身的方法,就執行本身的;若是子類沒有本身的方法,就會找父類的
3)若是子類裏面沒有找到,父類裏也沒有找到,就會報錯
六、在子類派生出新的功能中如何重用父類的功能
如今我定義一個學生類,隸屬於學校 「湫兮如風」 ,學生有姓名,年齡,性別,還能夠選課;再建立教師類,也隸屬於學校 「湫兮如風」 ,教師有姓名,年齡,性別,等級,工資,教師能夠給學生打分
class Student(): school = '湫兮如風學院'
def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def choose_course(self): print('%s正在選課' %self.name) class Teacher(): school = '湫兮如風學院'
def __init__(self, name, age, gender, level, salary): self.name = name self.age = age self.gender = gender self.level = level self.salary = salary def score(self, stu, num): print('教師%s給學生%s打%s分' %(self.name, stu.name, num)) stu.num = num
可是這樣寫有不少重複的代碼,分析一下條件,教師和學生都屬於學院的人,因而能夠定義出一個父類讓教師和學生去繼承這個父類
class People(): school = '湫兮如風學院'
class Student(People): def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def choose_course(self): print('%s正在選課' %self.name) class Teacher(People): def __init__(self, name, age, gender, level, salary): self.name = name self.age = age self.gender = gender self.level = level self.salary = salary def score(self, stu, num): print('教師%s給學生%s打%s分' %(self.name, stu.name, num)) stu.num = num
這樣的處理還不夠完全,由於還有 __init__ 裏面還有部分重複的代碼,能夠將這些重複的代碼放入父類,可是教師類中還有等級和工資沒法處理,這兩個對於父類來講就是多出來的,會報錯
class People(): school = '湫兮如風學院'
def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(People): def choose_course(self): print('%s正在選課' %self.name) class Teacher(People): def score(self, stu, num): print('教師%s給學生%s打%s分' %(self.name, stu.name, num)) stu.num = num stu = Student('qiu', 22, 'male') tea = Teacher('xi', 20, 'male', 10, 3000) # 運行
TypeError: __init__() takes 4 positional arguments but 6 were given
能夠在教師類中派生新的 __init__ ,將教師的姓名,年齡,性別,等級,工資寫入,但這又回到了上一步,又有了重複的代碼,這就引入了須要解決的問題:在子類派生出新的功能中如何重用父類的功能?
在函數中咱們介紹了調用的概念,在父類中有這個功能,在子類中不想再寫,就能夠調用這個功能
class People(): school = '湫兮如風學院'
def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(People): def choose_course(self): print('%s正在選課' %self.name) class Teacher(People): def __init__(self, name, age, gender, level, salary): # 注意: 這裏不能使用self去調用__init__, 這樣調用的永遠是本身的__init__,
# 由於這裏的self是Teacher的一個對象, 就是tea, tea先從本身去找__init__, 沒有找到
# 再到Teacher中找__init__, 找到了, 因而永遠在調用本身的__init__, 沒法訪問到父類的__init__
# 因而用類名去調, 這實際上就是一個普通函數, 沒有自動傳值的功能, 須要將參數一個很多的傳入
People.__init__(self, name, age, gender) self.level = level self.salary = salary def score(self, stu, num): print('教師%s給學生%s打%s分' %(self.name, stu.name, num)) stu.num = num stu = Student('qiu', 22, 'male') tea = Teacher('xi', 20, 'male', 10, 3000)
這是第一種方式,指名道姓的訪問某一個類中的函數,這實際上與面向對象無關,即使是 Teacher 和 Person 沒有繼承關係,也可以調用
在講第二種方式以前,須要先了解在繼承背景下,經典類與新式類的屬性查找順序
首先是單繼承,在單繼承背景下,不管是新式類仍是經典類屬性查找順序都同樣,都是先從對象中查找,對象中沒有到類中,類中沒有再到父類中。
而在多繼承背景下,若是一個子類繼承多個父類,但這些父類沒有再繼承同一個非 object 的父類,在這種狀況下,新式類仍是經典類屬性查找順序也是同樣的,先在對象中查找,對象中沒有再到對象所在的類中,再沒有就會按照從左到右的順序一個父類一個父類的找下去
因此這裏的查找順序是:obj ---> A ---> B ---> E ---> C ---> F ---> D,D中沒有再到 object 中查找,在這裏 object 不在討論範圍以內,好比要查找 name 屬性,object類 中不可能會去定義一個 name 屬性。下面是代碼實現
class E(): x = 'E'
pass
class F(): X = 'F'
pass
class B(E): # x = 'B'
pass
class C(F): x = 'C'
pass
class D(): x = 'D'
pass
class A(B, C, D): # x = 'A'
pass obj = A() print(obj.x)
還有一種狀況,被稱之爲 「菱形繼承」 ,能夠抽象的想像到,最終的父類是匯聚到一點,繼承了同一個父類,而且這個父類也不是 object 類。在這種狀況下,新式類和經典類的屬性查找順序不一樣。
在新式類中,是按照廣度優先查找,即:obj ---> A ---> B ---> E ---> C ---> F ---> D ---> G
在經典類中,是按照深度優先查找,即:obj ---> A ---> B ---> E ---> G ---> C ---> F ---> D
對於定義的每個類,Python 會計算出一個方法解析順序(MRO)列表,這個 MRO 列表就是用於保存繼承順序的一個列表
class G(): x = 'G'
pass
class E(G): x = 'E'
pass
class F(G): X = 'F'
pass
class B(E): x = 'B'
pass
class C(F): x = 'C'
pass
class D(G): x = 'D'
pass
class A(B, C, D): x = 'A'
pass obj = A() # print(obj.x)
print(A.mro()) # 運行結果
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
爲了實現繼承,Python 會在 MRO 列表上從左到右開始查找父類,直到找到第一個匹配這個屬性的類爲止,而這個 MRO 列表的構造是經過一個 C3 線性化算法來實現的。咱們不去深究這個算法的數學原理,它實際上就是合併全部父類的 MRO 列表並遵循以下三條準則:
1)子類會先於父類被檢查
2)若是有多個父類,則根據繼承語法在列表內的書寫順序被檢查
3)若是多個類繼承了同一個父類,則子類中只會選取繼承語法括號中的第一個父類
瞭解了以上知識點,如今繼續來講在子類派生出的新功能中如何重用父類功能的方式二
有一個叫作 super 的內置函數,調用該函數會獲得一個特殊的對象,該對象是專門用來訪問父類中屬性和方法
class People(): school = '湫兮如風學院'
def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(People): def choose_course(self): print('%s正在選課' %self.name) class Teacher(People): def __init__(self, name, age, gender, level, salary): super(Teacher, self).__init__(name, age, gender) self.level = level self.salary = salary def score(self, stu, num): print('教師%s給學生%s打%s分' %(self.name, stu.name, num)) stu.num = num stu = Student('qiu', 22, 'male') tea = Teacher('xi', 20, 'male', 10, 3000)
強調:super 會嚴格參照類的 MRO 列表依次查找屬性
這是子類在類裏面調用父類方法,使用 super(子類類名,self).方法名() 或 super().__init__(參數),在 Python3 中 super 能夠不傳參數,Python2 中必需要傳參數