繼承與派生與多型性

2020-08-10 10:17:50

繼承與派生

​ 在python中,新建的類可以繼承一個或多個父類別(有些語言不能繼承多個父類別),這種類可以被稱爲子類或派生類,父類別則可以被稱爲基礎類別或超類

經典類與新式類

​ 在python2中有經典類與新式類之分,然而在python3中則沒有這種區分,統一爲新式類。新式類與經典類最大的區別就是有無繼承object類,在python3中不論是否顯示繼承object類,預設都是繼承object類的

class Parent:
    pass
class Sub(Parent):
    pass
print(Sub.__bases__)
print(Parent.__bases__)

>>>(<class '__main__.Parent'>,)
>>>(<class 'object'>,)

​ 至於新式類與繼承類的區別我們稍後再談

繼承與抽象

​ 使用父類別與子類究其原因是因爲我們需要減少程式碼的冗餘,我們將多個類中的重複功能或者程式碼提取出來,把他拼成一個父類別,然後我們再通過繼承父類別去減少程式碼的冗餘。而這個提取的過程就是抽象。

同時我們需要瞭解一個類與一個類如果是繼承的關係,那麼他們兩者之間必須遵守的是a is b 的關係 b是父類別 a是子類 在後文中提到的組合則是a have b的關係

父類別與子類的尋找順序
class Teacher:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

class Student(Teacher):
    def test(self):
        print(self.name)
student = Student('施',18,'男')
student.test()

如上所示,若我們沒有再字類中定義 init 函數,他會從父類別中進行尋找。

class Teacher:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def test(self):
        print(self.age)
class Student(Teacher):

    def test(self):
        print(self.name)
        print(self.age)
student = Student('施',18,'男')
student.test()
>>>>>>18

如上所示,若是再父類別與字類中有衝突的方法的話,將會優先尋找自己本身中的方法

因此綜上所述。若你定義了一個字類的函數,並且再父類別中有相同的方法那麼會優先再本身中去找,找不到則去父類別中找

菱形問題

​ 大多數語言都不支援多繼承,python則不一樣,一個子類可以繼承多個父類別,這固然可以解決程式碼的重用問題,但是也有可能引發著名的 Diamond problem菱形問題

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-W2eRQBmo-1597025837043)(C:\Users\QAQ\Desktop\python學習\圖片\物件導向\繼承與派生\鑽石菱形問題.png)]

鑽石菱形問題

如上圖,這就是鑽石菱形問題。

它的含義是當b,c當中同時有相同的方法,那麼d應該繼承誰的那個方法呢?

而python是怎麼解決這個問題的呢?

深度優先與廣度優先

python中有兩種尋找的方法

1.深度優先

​ 在經典類中纔有的也就是該方法

class A:
    pass
class B(A):
    pass
class C(A):
    pass
class D(B):
    pass
class E(C):
    pass
class F(D):
    pass
class G(E):
    pass
class H(G,F):
    pass
#例如該種形式,最後所有的函數都會繼承A類
#他們的尋找順序則是G->E->C->A然後再去找另一條路線

2.廣度優先

class A:
    pass
class B(A):
    pass
class C(A):
    pass
class D(B):
    pass
class E(C):
    pass
class F(D):
    pass
class G(E):
    pass
class H(G,F):
    pass
#例如該種形式,最後所有的函數都會繼承A類
#廣度優先的尋找順序則是G->E->C然後再去找另一條路線,最後再去找A

綜上所述:新式類與經典類的最大區別就是解決菱形問題的方式不一樣,其餘大致一致

python Mixins機制 機製

​ 在現實中我們是極爲不提倡使用多繼承的方式去解決程式碼重用的問題的,因爲 首先這樣的設計可能會導致菱形問題 , 其次與我們的設計的邏輯也往往有一些衝突,但是在編寫程式碼的時候往往會出現一些多個類有重複的程式碼,但是放在上面的父類別卻又不適合的情況。

​ 在java中提供了介面方法去解決這個情況。

​ 而在python中並沒有介面的這種概念,因此他規定在類方法後加 Mixin以用來區分父類別與普通介面的情況

class RunMixin:
    def run(self):
        pass
    pass


class Animal:
    pass


class Fish(Animal):
    pass


class Dog(Animal,RunMixin):
    pass


class Cat(Animal,RunMixin):
    pass

​ 當然這些介面與我們平時見的類也並無本質區別,加的後綴也更像是一種約定俗稱的做法。

​ 這主要也是與python的設計理念有很大的關係

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

​ 1.首先使用Mixin它必須代表的是某一種功能,而不是一種物品,不然不符合 「is-a」原則

​ 2.一個Mixin只寫一種方法,對於多個方法我們可以採用使用多個Mixin來解決程式碼冗餘問題

​ 3.它不依賴於子類的實現

​ 4.子類即使沒有繼承這個類,也不會影響工作

​ Mixin確實在很大程度上解決了我們的問題,但是也有一定的缺陷

​ 當我們定義了多個Mixin時我們很容易陷入程式碼的可讀性下降得情況

派生與方法重用

​ 派生:本指江河的源頭產生出支流。現引申爲從一個主要事物的發展中分化出來,這裏指的是子類擴充套件出自己的新方法或屬性

​ 由上文的父類別與子類的尋找順序我們可以知道,尋找順序會優先從子類中尋找,因此當我們的子類需要擴充套件一些方法或屬性時,我們新寫的方法與屬性就會覆蓋原父類別的方法或屬性。

​ 那當我們需要呼叫父類別中的屬性或方法時該怎麼做呢。

在python中有兩種方法

1.直接使用 父類別名.方法名或函數名

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


class Teacher(People):
    def __init__(self,name,age,sex,address):
        People.__init__(self,name,age,sex)
        self.address = address
#因爲是直接呼叫因此需要幾個參數就給幾個參數,self不能忘記

2.super()

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


class Teacher(People):
    def __init__(self,name,age,sex,address):
        #自動對父類別傳入self,並且嚴格遵守MRO規定
        super().__init__(name,age,sex)
        self.address = address
        print(name,sex,address,age)

teacher = Teacher('施',18,'male','蕭山')

​ 該兩種方式的區別是第一種是不依賴於繼承關係的,第二種則是依賴於繼承的,這兩種方式都可以使用,但是建議使用第二種

組合

上面說到繼承是一種「a is b"的關係,那麼組合就是一種」a 有 b「 的關係

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類
class Teacher(People): #老師是人
    def __init__(self,name,sex,age,title,year,mon,day):
        super().__init__(name,age,sex)
        self.birth=Date(year,mon,day) #老師有生日
    def teach(self):
        print('%s is teaching' %self.name)


teacher = Teacher('施','male','18','1','2019','10','1')

多型性

多型與多型性

多型:一種事物的多種形態,反應到物件導向中指的是父類別中的一種方法,在不同的子類中展示出不同的形態。

class Animal:
    def bark(self):
        pass
    pass
class Dog(Animal):
    def bark(self):
        print('汪汪汪')
class Cat(Animal):
    def bark(self):
        print('喵喵喵')

​ 如上述程式碼所示動物的形態之一就是狗,形態之二就是貓

​ 這體現的就是一種多型

​ 而多型性指的就是在不同類中雖然形態不一樣,但是擁有相同的方法,這就要求我們在設計的時候就需要把物件的使用方法統一成一種

類似java介面的方法

​ 你若是需要使底下的各個類中的方法統一名稱,你可以匯入abc模組

import abc
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def bark(self):
        pass
    pass
class Dog(Animal):
    def talk(self):
        pass

class Cat(Animal):
    def bark(self):
        print('喵喵喵')
dog = Dog()
dog.talk()
#這樣就會報錯
>>>TypeError: Can't instantiate abstract class Dog with abstract methods bark

​ 但是在python中並不推薦這種方式。

鴨子型別

​ python認爲這種方式會有繼承的一些弊端。他更推崇於程式設計師自身在設計以及編寫程式碼時就自己自發的將方法名稱進行統一。及不使用繼承的方式,這種也被稱爲鴨子型別(看起來是鴨子那他就是鴨子)

class Dog:
    def dark(self):
        print('汪汪汪')
        pass

class Cat:
    def bark(self):
        print('喵喵喵')
dog = Dog()
dog.dark()