繼承


1.繼承介紹

1.1什么是繼承

繼承是一種創建新類的方式,在Python中,新建的類可以繼承一個或多個父類,新建的類可稱為子類或派生類,父類又可稱為基類或超類

class Parent1:
    x = 11

class Parent2:
    pass

class Sub1(Parent1):
    pass

class Sub2(Parent1, Parent2):
    pass

通過類的內置屬性__bases__可以查看類繼承的所有父類

print(Sub1.__bases__)  # 查看sub1的父類
print(Sub2.__bases__)  # 查看sub2的父類

補充 :

ps1 : 在python2中有經典類和新式類
         新式類:繼承了object類的子類,以及子子類的子類
         經典類:沒有繼承object類的子類,以及子類的子類

ps2 : python3中的類默認是新式類,即默認繼承object類

1.2為什么要繼承以及繼承的意義

為什么要用繼承
解決類與類之間代碼冗余問題

繼承的意義
子類會遺傳父類的屬性,解決類與類之間代碼冗余問題

2.繼承和抽象

要找出類與類之間的繼承關系,需要先抽象,再繼承。抽象即總結相似之處,總結對象之間的相似之處得到類,總結類與類之間的相似之處就可以得到父類,如下圖所示

image-20211008220039798

基於抽象的結果,我們就找到了繼承關系

image-20211008220059680

基於上圖我們可以看出類與類之間的繼承指的是什么’是’什么的關系(比如人類,豬類,猴類都是動物類)。子類可以繼承/遺傳父類所有的屬性,因而繼承可以用來解決類與類之間的代碼重用性問題。比如我們按照定義Student類的方式再定義一個Teacher類

class Teacher:
    school='清華大學'

    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age

    def teach(self):
        print('%s is teaching' %self.name)

類Teacher與Student之間存在重復的代碼,老師與學生都是人類,所以我們可以得出如下繼承關系,實現代碼重用

class People:
    school='清華大學'

    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age

class Student(People):
    def choose(self):
        print('%s is choosing a course' %self.name)

class Teacher(People):
    def teach(self):
        print('%s is teaching' %self.name)

Teacher類內並沒有定義__init__方法,但是會從父類中找到__init__,因而仍然可以正常實例化,如下

>>> teacher1=Teacher('lili','male',18)
>>> teacher1.school,teacher1.name,teacher1.sex,teacher1.age
('清華大學', 'lili', 'male', 18)

這整個的過程就是我們平常寫代碼 , 怎么樣寫出來繼承的 , 先分開寫類 , 然后有重復的代碼 , 歸納到父類中繼承

3.屬性查找

1.單繼承下的屬性查找

有了繼承關系,對象在查找屬性時,先從對象自己的__dict__中找,如果沒有則去當前類中找,然后再去父類中找

class Foo:
    def f1(self):
        print("foo.f1")

    def f2(self):
        print("foo.f2")
        self.f1()


class Bar(Foo):
    def f1(self):
        print("bar.f1")


b = Bar()
b.f2()

# 只要涉及到繼承找屬性,一定要先從當前對象查找,沒有,再到類中查找,如果在沒有再到父類中查找

父類如果不想讓子類覆蓋自己的方法,可以采用雙下划線開頭的方式將方法設置為私有的

class Foo:     def __f1(self): # 變形為_Foo__fa         print('Foo.f1')      def f2(self):         print('Foo.f2')        self.__f1() # 變形為self._Foo__fa,因而只會調用自己所在的類中的方法class Bar(Foo):     def __f1(self): # 變形為_Bar__f1        print('Foo.f1') b=Bar()b.f2() #在父類中找到f2方法,進而調用b._Foo__f1()方法,同樣是在父類中找到該方法Foo.f2Foo.f1

關於多繼承下的屬性查找 , 會涉及到菱形問題 , 以及python多繼承的優缺點 , 和mixins機制 , 下面單獨具體介紹

4.繼承實現的原理

1.python多繼承的優缺點

優點:子類可以同時遺傳多個父類的屬性,最大限度的重用代碼    缺點:1.違背人的思維習慣:繼承表達的是一種什么"是"什么的關系,多繼承就會出現一個東西是好幾種東西        2.代碼可讀性比較差        3.不建議使用多繼承,有可能產生菱形問題,擴展性變差,        如果一個子類不可避免地要重用多個父類的屬性,應該使用Mixins機制

2.菱形問題

大多數面向對象語言都不支持多繼承,而在Python中,一個子類是可以同時繼承多個父類的,這固然可以帶來一個子類可以對多個不同父類加以重用的好處,但也有可能引發著名的 Diamond problem菱形問題(或稱鑽石問題,有時候也被稱為“死亡鑽石”),菱形其實就是對下面這種繼承結構的形象比喻

image-20211008221406972

A類在頂部,B類和C類分別位於其下方,D類在底部將兩者連接在一起形成菱形。

這種繼承結構下導致的問題稱之為菱形問題:如果A中有一個方法,B和/或C都重寫了該方法,而D沒有重寫它,那么D繼承的是哪個版本的方法:B的還是C的?如下所示

class A(object):    def test(self):        print('from A')class B(A):    def test(self):        print('from B')class C(A):    def test(self):        print('from C')class D(B,C):    passobj = D()obj.test() # 結果為:from B

要想搞明白obj.test()是如何找到方法test的,需要了解python的繼承實現原理

3.繼承的原理

python到底是如何實現繼承的呢? 對於你定義的每一個類,Python都會計算出一個方法解析順序(MRO)列表,該MRO列表就是一個簡單的所有基類的線性順序列表,如下

print(D.mro()) # 新式類內置了mro方法可以查看線性列表的內容,經典類沒有該內置該方法[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

python會在MRO列表上從左到右開始查找基類, 直到找到第一個匹配這個屬性的類為止。 而這個MRO列表的構造是通過一個C3線性化算法來實現的

4.深度優先和廣度優先

如果繼承關系為菱形結構,那么經典類與新式類會有不同MRO,分別對應屬性的兩種查找方式:深度優先和廣度優先

image-20211008221757527

class G: # 在python2中,未繼承object的類及其子類,都是經典類    def test(self):        print('from G')class E(G):    def test(self):        print('from E')class F(G):    def test(self):        print('from F')class B(E):    def test(self):        print('from B')class C(F):    def test(self):        print('from C')class D(G):    def test(self):        print('from D')class A(B,C,D):    # def test(self):    #     print('from A')    passobj = A()obj.test() # 如上圖,查找順序為:obj->A->B->E->G->C->F->D->object# 可依次注釋上述類中的方法test來進行驗證,注意請在python2.x中進行測試

image-20211008221835880

class G(object):    def test(self):        print('from G')class E(G):    def test(self):        print('from E')class F(G):    def test(self):        print('from F')class B(E):    def test(self):        print('from B')class C(F):    def test(self):        print('from C')class D(G):    def test(self):        print('from D')class A(B,C,D):    # def test(self):    #     print('from A')    passobj = A()obj.test() # 如上圖,查找順序為:obj->A->B->E->C->F->D->G->object# 可依次注釋上述類中的方法test來進行驗證

小結

python2中經典類多繼承遇到菱形問題是深度優先python3中新式類多繼承遇到菱形問題是廣度優先

5.Pyton Mixins機制

一個子類可以同時繼承多個父類,這樣的設計常被人詬病,一來它有可能導致可惡的菱形問題,二來在人的世界觀里繼承應該是個”is-a”關系。 比如轎車類之所以可以繼承交通工具類,是因為基於人的世界觀,我們可以說:轎車是一個(“is-a”)交通工具,而在人的世界觀里,一個物品不可能是多種不同的東西,因此多重繼承在人的世界觀里是說不通的,它僅僅只是代碼層面的邏輯。不過有沒有這種情況,一個類的確是需要繼承多個類呢?

答案是有,我們還是拿交通工具來舉例子:

民航飛機、直升飛機、轎車都是一個(is-a)交通工具,前兩者都有一個功能是飛行fly,但是轎車沒有,所以如下所示我們把飛行功能放到交通工具這個父類中是不合理的

class Vehicle:  # 交通工具    def fly(self):        '''        飛行功能相應的代碼                '''        print("I am flying")class CivilAircraft(Vehicle):  # 民航飛機    passclass Helicopter(Vehicle):  # 直升飛機    passclass Car(Vehicle):  # 汽車並不會飛,但按照上述繼承關系,汽車也能飛了    pass

但是如果民航飛機和直升機都各自寫自己的飛行fly方法,又違背了代碼盡可能重用的原則(如果以后飛行工具越來越多,那會重復代碼將會越來越多)。

怎么辦???為了盡可能地重用代碼,那就只好在定義出一個飛行器的類,然后讓民航飛機和直升飛機同時繼承交通工具以及飛行器兩個父類,這樣就出現了多重繼承。這時又違背了繼承必須是”is-a”關系。這個難題該怎么解決?

python提供了Mixins機制,簡單來說Mixins機制指的是子類混合(mixin)不同類的功能,而這些類采用統一的命名規范(例如Mixin后綴),以此標識這些類只是用來混合功能的,並不是用來標識子類的從屬"is-a"關系的,所以Mixins機制本質仍是多繼承,但同樣遵守”is-a”關系,如下

class Vehicle:  # 交通工具    passclass FlyableMixin:    def fly(self):        '''        飛行功能相應的代碼                '''        print("I am flying")class CivilAircraft(FlyableMixin, Vehicle):  # 民航飛機    passclass Helicopter(FlyableMixin, Vehicle):  # 直升飛機    passclass Car(Vehicle):  # 汽車    pass# ps: 采用某種規范(如命名規范)來解決具體的問題是python慣用的套路

簡單總結 : 多繼承中遇到混合繼承 , 那么就采用Mixins機制解決 , 注意這不是語法嚴格要求的 , 這是約定俗成的

使用Mixin類實現多重繼承要非常小心

  • 首先它必須表示某一種功能,而不是某個物品,python 對於mixin類的命名方式一般以 Mixin, able, ible 為后綴
  • 其次它必須責任單一,如果有多個功能,那就寫多個Mixin類,一個類可以繼承多個Mixin,為了保證遵循繼承的“is-a”原則,只能繼承一個標識其歸屬含義的父類
  • 然后,它不依賴於子類的實現
  • 最后,子類即便沒有繼承這個Mixin類,也照樣可以工作,就是缺少了某個功能。(比如飛機照樣可以載客,就是不能飛了)

5.派生與方法重用

子類可以派生出自己新的屬性,在進行屬性查找時,子類中的屬性名會優先於父類被查找 , 那么在子類派生的新方法中如何重用父類的功能???

方式1: 指名道姓調用某一類下的函數=>不依賴繼承關系

# 重用父類中的__init__方法class Foo:    def __init__(self, name, age, sex):        self.name = name        self.age = age        self.sex = sexclass Teacher(Foo):    def __init__(self, name, age, sex, salary, level):        Foo.__init__(self, name, age, sex)        self.salary = salary        self.level = level

方式2: super()調用父類提供給自己的方法=>嚴格繼承關系

class Foo1:    def __init__(self, name, age, sex):        self.name = name        self.age = age        self.sex = sexclass Teacher1(Foo1):    def __init__(self, name, age, sex, salary, level):        # super(Foo1,self).__init__(name, age, sex)        super().__init__(name, age, sex)  # 調用的是方法,自動傳入對象        self.salary = salary        self.level = level        # 調用 super()會得到一個特殊的對象,該對象會參照發起屬性查找的那個類的mro,去當前類的父類中找屬性

super方法會在后面我們重寫父類方法的時候必須先加上這一行代碼 , 為的是不破壞原來父類中該方法的代碼 , 我們只是重用並新增一些代碼 , 即在原來的基礎上追加

6.組合

在一個類中以另外一個類的對象作為數據屬性,稱為類的組合。就是把對象當成屬性

組合與繼承都是用來解決代碼的重用性問題。不同的是:繼承是一種“是”的關系,比如老師是人、學生是人,當類之間有很多相同的之處,應該使用繼承;而組合則是一種“有”的關系,比如老師有生日,老師有多門課程,當類之間有顯著不同,並且較小的類是較大的類所需要的組件時,應該使用組合,如下示例

class Course:    def __init__(self,name,period,price):        self.name=name        self.period=period        self.price=price    def tell_info(self):        print('<%s %s %s>' %(self.name,self.period,self.price))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 People:    school='清華大學'    def __init__(self,name,sex,age):        self.name=name        self.sex=sex        self.age=age#Teacher類基於繼承來重用People的代碼,基於組合來重用Date類和Course類的代碼class Teacher(People): #老師是人    def __init__(self,name,sex,age,title,year,mon,day):        super().__init__(name,age,sex)        self.birth=Date(year,mon,day) #老師有生日        self.courses=[] #老師有課程,可以在實例化后,往該列表中添加Course類的對象    def teach(self):        print('%s is teaching' %self.name)python=Course('python','3mons',3000.0)linux=Course('linux','5mons',5000.0)teacher1=Teacher('lili','female',28,'博士生導師',1990,3,23)# teacher1有兩門課程teacher1.courses.append(python)teacher1.courses.append(linux)# 重用Date類的功能teacher1.birth.tell_birth()# 重用Course類的功能for obj in teacher1.courses:     obj.tell_info()

此時對象teacher1集對象獨有的屬性、Teacher類中的內容、Course類中的內容於一身(都可以訪問到),是一個高度整合的產物


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM