面向對象之繼承

一、繼承的概念算法

繼承是一種新建類的方式,新建類能夠建立一個或多個父類,父類又能夠稱之爲基類或超類,新建的類稱之爲子類或派生類,繼承描述的是一種遺傳關係,子類能夠重用父類的屬性和方法。使用繼承能夠減小類與類之間代碼冗餘的問題。編程

在 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'>,)
__bases__

若是沒有指定父類,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
View Code

可是這樣寫有不少重複的代碼,分析一下條件,教師和學生都屬於學院的人,因而能夠定義出一個父類讓教師和學生去繼承這個父類

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
View Code

這樣的處理還不夠完全,由於還有 __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
View Code

能夠在教師類中派生新的 __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)
View Code

這是第一種方式,指名道姓的訪問某一個類中的函數,這實際上與面向對象無關,即使是 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)
View Code

還有一種狀況,被稱之爲 「菱形繼承」 ,能夠抽象的想像到,最終的父類是匯聚到一點,繼承了同一個父類,而且這個父類也不是 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'>]
新式類查找順序及MRO

爲了實現繼承,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

強調:super 會嚴格參照類的 MRO 列表依次查找屬性

這是子類在類裏面調用父類方法,使用 super(子類類名,self).方法名() 或 super().__init__(參數),在 Python3 中 super 能夠不傳參數,Python2 中必需要傳參數

相關文章
相關標籤/搜索