1. 程式人生 > >python的三大特性之繼承

python的三大特性之繼承

繼承

繼承是一種建立新類的方式,在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__)的實現。
在這裡插入圖片描述

繼承與抽象(先抽象再繼承)

繼承:是基於抽象的結果,通過程式語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。

抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類

抽象的層次越高,軟體的複用程度越大。抽象資料型別是實現軟體模組化設計思想的重要手段。

繼承與重用性的例子

class Animal:
    def __init__(self, name, sex, kind):
        self.name = name
        self.sex = sex
        self.kind = kind
    def eat(self):
        return '%s 吃了' % (self.name)
    def drink(self):
        return '%s喝了' % (self.name)
 
class Cat(Animal):
    def func(self):
        return "climbing"
 
class Dog(Animal):
    def func(self):
        return "watching door"
 
miao = Cat('miao', '女', '波斯貓')
print(miao.eat())
print(miao.func())
 
wangwang = Dog('wang', '男', 'teddy')
print(wangwang.eat())
print(wangwang.func())

用已經有的類建立一個新的類,這樣就重用了已經有的軟體中的一部分設定大部分,大大生了程式設計工作量,這就是常說的軟體重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定製新的資料型別,這樣就是大大縮短了軟體開發週期,對大型軟體開發來說,意義重大.

派生

1.在父類的基礎上產生子類,產生的子類就叫做派生類

2.父類裡沒有的方法,在子類中有了,這樣的方法就叫做派生方法。

3.父類裡有,子類也有的方法,就叫做方法的重寫(就是把父類裡的方法重寫了)

注意的幾個概念:

1.子類可以使用父類的所有屬性和方法

2.如果子類有自己的方法,就執行自己的;如果子類沒有自己的方法,就會找父類的。

3.如果子類裡面沒有找到,父類裡也沒有找到,就會報錯

4.如果子類中實現了呼叫父類的方法

在類內:super(子類,self).方法名() supper().init(引數)

在類外:super(子類名,物件名).方法名()

class Animal:
    '''
    人和狗都是動物,所以創造一個Animal基類
    '''
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有自己的暱稱;
        self.aggressivity = aggressivity  # 人和狗都有自己的攻擊力;
        self.life_value = life_value  # 人和狗都有自己的生命值;

    def eat(self):
        print('%s is eating'%self.name)

class Dog(Animal):
    '''
    狗類,繼承Animal類
    '''
    def bite(self, people):
        '''
        派生:狗有咬人的技能
        :param people:  
        '''
        people.life_value -= self.aggressivity

class Person(Animal):
    '''
    人類,繼承Animal
    '''
    def attack(self, dog):
        '''
        派生:人有攻擊的技能
        :param dog: 
        '''
        dog.life_value -= self.aggressivity

egg = Person('egon',10,1000)
ha2 = Dog('二愣子',50,1000)
print(ha2.life_value)
print(egg.attack(ha2))
print(ha2.life_value)

在子類中,新建的重名的函式屬性,在編輯函式內功能的時候,有可能需要重用父類中重名的那個函式功能,應該是用呼叫普通函式的方式,即:類名.func(),此時就與呼叫普通函式無異了,因此即便是self引數也要為其傳值.

在python3中,子類執行父類的方法也可以直接用super方法.

關於super

class Animal:
    '''
    人和狗都是動物,所以創造一個Animal基類
    '''
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有自己的暱稱;
        self.aggressivity = aggressivity  # 人和狗都有自己的攻擊力;
        self.life_value = life_value  # 人和狗都有自己的生命值;
 
    def eat(self):
        print('%s is eating'%self.name)
 
class Dog(Animal):
    '''
    狗類,繼承Animal類
    '''
    def __init__(self,name,breed,aggressivity,life_value):
        super().__init__(name, aggressivity, life_value) #執行父類Animal的init方法
        self.breed = breed  #派生出了新的屬性
 
    def bite(self, people):
        '''
        派生出了新的技能:狗有咬人的技能
        :param people: 
        '''
        people.life_value -= self.aggressivity
 
    def eat(self):
        # Animal.eat(self)
        #super().eat()
        print('from Dog')
 
class Person(Animal):
    '''
    人類,繼承Animal
    '''
    def __init__(self,name,aggressivity, life_value,money):
        #Animal.__init__(self, name, aggressivity, life_value)
        #super(Person, self).__init__(name, aggressivity, life_value)
        super().__init__(name,aggressivity, life_value)  #執行父類的init方法
        self.money = money   #派生出了新的屬性
 
    def attack(self, dog):
        '''
        派生出了新的技能:人有攻擊的技能
        :param dog:
        '''
        dog.life_value -= self.aggressivity
 
    def eat(self):
        #super().eat()
        Animal.eat(self)
        print('from Person')
 
egg = Person('egon',10,1000,600)
ha2 = Dog('二愣子','哈士奇',10,1000)
print(egg.name)
print(ha2.name)
egg.eat()

通過繼承建立了派生類與基類之間的關係,它是一種’是’的關係,當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好。

抽象類與介面類

源於Java的程式設計原則和設計模式,因為Java是面向物件,但Java不能實現多繼承。

介面類
繼承有兩種用途:

一:繼承基類的方法,並且做出自己的改變或者擴充套件(程式碼重用)

二:宣告某個子類兼容於某基類,定義一個介面類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)

但容易出現錯誤,下面這個例子中就會報錯

class Alipay:
    '''
    支付寶支付
    '''
    def pay(self,money):
        print('支付寶支付了%s元'%money)
 
class Applepay:
    '''
    apple pay支付
    '''
    def pay(self,money):
        print('apple pay支付了%s元'%money)
 
class Wechatpay:
    def fuqian(self,money):
        '''
        實現了pay的功能,但是名字不一樣
        '''
        print('微信支付了%s元'%money)
 
def pay(payment,money):
    '''
    支付函式,總體負責支付
    對應支付的物件和要支付的金額
    '''
    payment.pay(money)
 
 
p = Wechatpay()
pay(p,200)   #執行會報錯

我們可能在測試的時候,不小心漏掉了一個用例,但只要不使用這個用例進行呼叫,就不會報錯,我們可以借用ABC模組來實現介面,就不會出現這樣的錯誤,在不例項化的時候,他就已經報錯了,因為你在這個裡面進行了約束,當你的物件繼承了該弗雷,你就要實現@abstractmethod的這樣類似的方法,否則就會報錯。有利於我們程式設計師差錯,也儘可能不出錯

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中,根本沒有介面的概念,只是程式碼看起來像而已,因為Python自身可以實現多繼承,所以不需要介面的概念,這是從Java裡引用而來的。

介面提取了一群類共同的函式,可以把介面當做一個函式的集合。

然後讓子類去實現介面中的函式。

這麼做的意義在於歸一化,什麼叫歸一化,就是隻要是基於同一個介面實現的類,那麼所有的這些類產生的物件在使用時,從用法上來說都一樣。

歸一化,讓使用者無需關心物件的類是什麼,只需要的知道這些物件都具備某些功能就可以了,這極大地降低了使用者的使用難度。就下面這例子來講,它們這些物件最終
都實現了付款的功能,歸一化設計就是把他們這些具有相同的功能都使用同一個方法去實現。至於裡面的具體功能,在物件裡面的方法去寫。

# 建立一個規範
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):    # 抽象類 介面類  規範和約束  metaclass指定的是一個元類
    @abstractmethod
    def pay(self):pass  # 抽象方法
class Alipay(Payment):
    def pay(self,money):
        print('使用支付寶支付了%s元'%money)
 
class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付了%s元'%money)
 
class Wechatpay(Payment):
    def pay(self,money):
        print('使用微信支付了%s元'%money)
    def recharge(self):pass
 
def pay(a,money):
    a.pay(money)
 
a = Alipay()
a.pay(100)
pay(a,100)    # 歸一化設計:不管是哪一個類的物件,都呼叫同一個函式去完成相似的功能
q = QQpay()
q.pay(100)
pay(q,100)

抽象類

與java一樣,python也有抽象類的概念但是同樣需要藉助模組實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被例項化

為什麼要有抽象類

如果說類是從一堆物件中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括資料屬性和函式屬性。

比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。

從設計角度去看,如果類是從現實物件抽象而來的,那麼抽象類就是基於類抽象而來的。

從實現角度來看,抽象類與普通類的不同之處在於:抽象類中有抽象方法,該類不能被例項化,只能被繼承,且子類必須實現抽象方法。這一點與介面有點類似,但其實是不同的,

在python中實現抽象類

# 建立一個規範
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):    # 抽象類 介面類  規範和約束  metaclass指定的是一個元類
    @abstractmethod
    def pay(self):pass  # 抽象方法
class Alipay(Payment):
    def pay(self,money):
        print('使用支付寶支付了%s元'%money)
 
class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付了%s元'%money)
 
class Wechatpay(Payment):
    def pay(self,money):
        print('使用微信支付了%s元'%money)
    def recharge(self):pass
 
def pay(a,money):
    a.pay(money)
 
a = Alipay()
a.pay(100)
pay(a,100)    # 歸一化設計:不管是哪一個類的物件,都呼叫同一個函式去完成相似的功能
q = QQpay()
q.pay(100)
pay(q,100)
w = Wechatpay()
pay(w,100)

抽象類與介面類

抽象類的本質還是類,指的是一組類的相似性,說白了,局是一種程式碼的約束。減少程式碼的犯錯機率。包括資料屬性(如all_type)和函式屬性(如read、write),而介面只強調函式屬性的相似性。

抽象類是一個介於類和介面直接的一個概念,同時具備類和介面的部分特性,可以用來實現歸一化設計

在python中,並沒有介面類這種東西,即便不通過專門的模組定義介面,我們也應該有一些基本的概念。

1.多繼承問題
在繼承抽象類的過程中,我們應該儘量避免多繼承;
而在繼承介面的時候,我們反而鼓勵你來多繼承介面

介面隔離原則: 使用多個專門的介面,而不使用單一的總介面。即客戶端不應該依賴那些不需要的介面。

from abc import ABCMeta,abstractmethod
class FlyAnimal(metaclass=ABCMeta):
    @abstractmethod
    def fly(self):pass
    @abstractmethod
    def cal_flying_speed(self):pass
    @abstractmethod
    def cal_flying_height(self):pass
class WalkAnimal(metaclass=ABCMeta):
    @abstractmethod
    def walk(self):pass
class SwimAnimal(metaclass=ABCMeta):
    @abstractmethod
    def walk(self):pass
     
     
class Tiger(WalkAnimal,SwimAnimal):
    def walk(self):pass
    def swim(self):pass
class Monkey:
    def walk(self):pass
    def climb(self):pass
class Swan(FlyAnimal,WalkAnimal,SwimAnimal):
    def swim(self):pass
    def walk(self):pass
    def fly(self):pass
    def cal_flying_speed(self):pass
    def cal_flying_height(self):pass
class Parrot(FlyAnimal):
    def fly(self):pass
    def cal_flying_speed(self):pass
    def cal_flying_height(self): pass

方法的實現
在抽象類中,我們可以對一些抽象方法做出基礎實現;
而在介面類中,任何方法都只是一種規範,具體的功能需要子類實現

鑽石繼承

繼承順序
在這裡插入圖片描述
繼承順序的例子:

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):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有這個屬性可以檢視線性列表,經典類沒有這個屬性

#新式類繼承順序:F->D->B->E->C->A
#經典類繼承順序:F->D->B->A->E->C
#python3中統一都是新式類
#pyhon2中才分新式類與經典類

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

在這裡插入圖片描述
為了實現繼承,python會在MRO列表上從左到右開始查詢基類,直到找到第一個匹配這個屬性的類為止。
而這個MRO列表的構造是通過一個C3線性化演算法來實現的。我們不去深究這個演算法的數學原理,它實際上就是合併所有父類的MRO列表並遵循如下三條準則:
1.子類會先於父類被檢查
2.多個父類會根據它們在列表中的順序被檢查
3.如果對下一個類存在兩個合法的選擇,選擇第一個父類

繼承的作用

減少程式碼的重用
提高程式碼可讀性
規範程式設計模式

幾個名詞

抽象:抽象即抽取類似或者說比較像的部分。是一個從具題到抽象的過程。 繼承:子類繼承了父類的方法和屬性
派生:子類在父類方法和屬性的基礎上產生了新的方法和屬性

抽象類與介面類

1.多繼承問題 在繼承抽象類的過程中,我們應該儘量避免多繼承; 而在繼承介面的時候,我們反而鼓勵你來多繼承介面

2.方法的實現 在抽象類中,我們可以對一些抽象方法做出基礎實現; 而在介面類中,任何方法都只是一種規範,具體的功能需要子類實現

鑽石繼承

新式類:廣度優先

在這裡插入圖片描述
在這裡插入圖片描述
經典類:深度優先