日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

面向?qū)ο笈c繼承

面向?qū)ο笏枷胗腥笠兀?/p>

  1. 繼承
  2. 封裝
  3. 多態(tài)

面向?qū)ο缶幊蹋∣OP)語(yǔ)言的一個(gè)重要功能就是 “繼承”:

  • 它可以使用現(xiàn)有類(lèi)的所有功能,并在無(wú)需重新編寫(xiě)原來(lái)類(lèi)的情況下,對(duì)這些功能進(jìn)行擴(kuò)展
  • 通過(guò)繼承創(chuàng)建的新類(lèi)被稱(chēng)為 “子類(lèi)” 或 “派生類(lèi)”,被繼承的類(lèi)被稱(chēng)為 “基類(lèi)”、“父類(lèi)” 或 “超類(lèi)”
  • 在 Python/ target=_blank class=infotextkey>Python 中,同時(shí)支持單繼承與多繼承

繼承的概念

舉個(gè)例子,我們現(xiàn)在像創(chuàng)建豬、狗和貓三個(gè)類(lèi),它們都有名字和年齡屬性,也都有一個(gè)叫的方法。不同的是,豬有吃的方法、狗有看家的方法、貓有抓老鼠的方法。按照之前的學(xué)習(xí),我們會(huì)將代碼寫(xiě)成這樣:

class Pig:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def bark(self):
        print('叫')
    def eat(self):
        print('吃')

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def bark(self):
        print('叫')
    def guarding(self):
        print('看家')
        
class Cat:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def bark(self):
        print('叫')
    def catch(self):
        print('抓老鼠')

我們發(fā)現(xiàn),雖然實(shí)現(xiàn)了需求,但是我們看到,這里面出現(xiàn)了大量的重復(fù)代碼。如果我們能將這些重復(fù)代碼封裝起來(lái),比如封裝到一個(gè)動(dòng)物類(lèi)中,然后豬、狗和貓分別都繼承這個(gè)動(dòng)物類(lèi),就可以讓代碼更加簡(jiǎn)潔。

具體的實(shí)現(xiàn)方法為:

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def bark(self):
        print('叫')
class Pig(Animal):
    def eat(self):
        print('吃')
class Dog(Animal):
    def guarding(self):
        print('看家')
class Cat(Animal):
    def catch(self):
        print('抓老鼠')
mimi = Cat('咪咪', 3)
print(mimi.name, mimi.age)
mimi.bark()
mimi.catch()

輸出的結(jié)果為:

咪咪 3
叫
抓老鼠

實(shí)現(xiàn)繼承之后,子類(lèi)將繼承父類(lèi)的屬性和方法。

不難看出,繼承關(guān)系的特點(diǎn)為:

  • 增加了類(lèi)的耦合性(耦合性不宜多,宜精)
  • 減少了重復(fù)代碼
  • 使得代碼更加規(guī)范化,合理化

組合與繼承的對(duì)比:

  • 組合
  • 組合是指在新類(lèi)里面創(chuàng)建原有類(lèi)的對(duì)象,重復(fù)利用已有類(lèi)的功能,是 has-a 的關(guān)系(如:貓有腿)
  • 原來(lái)類(lèi)的對(duì)象作為整體,以新類(lèi)的屬性的形式存在
  • 繼承
  • 繼承允許設(shè)計(jì)人員根據(jù)其他類(lèi)的實(shí)現(xiàn)來(lái)定義一個(gè)類(lèi)的實(shí)現(xiàn),是 is-a 的關(guān)系(如:貓是動(dòng)物)
  • 子類(lèi)可以直接使用父類(lèi)中的屬性和方法,就好像父類(lèi)的屬性和方法已經(jīng)存在于子類(lèi)中了一樣

Python 3 中使用的都是新式類(lèi),如果一個(gè)類(lèi)誰(shuí)都不繼承,那么它默認(rèn)繼承 object 類(lèi)。

繼承雖然很好用,但是不能濫用,像之前說(shuō)的,耦合程度不宜過(guò)高,否則邏輯會(huì)十分混亂:

  • 不要輕易地使用繼承,除非兩個(gè)類(lèi)之間是 is-a 關(guān)系
  • 不要單純地為了實(shí)現(xiàn)代碼的重用而使用繼承,因?yàn)檫^(guò)多的繼承會(huì)破壞代碼的可維護(hù)性,當(dāng)父類(lèi)被修改的時(shí)候,會(huì)影響到所有繼承自它的子類(lèi),從而增加程序的維護(hù)難度與成本
  • 總結(jié)起來(lái)就是:組裝的時(shí)候使用組合,擴(kuò)展的時(shí)候使用繼承

回到我們剛才的例子,豬、狗、貓三各類(lèi)都只有動(dòng)物一個(gè)父類(lèi),這種只有一個(gè)父類(lèi)的繼承方式,我們稱(chēng)作為單繼承。在單繼承中,子類(lèi)可以繼承父類(lèi)的屬性和方法,修改父類(lèi),所有子類(lèi)都會(huì)受到影響。

isinstance和issubclass

isinstance:

  • 用于檢查實(shí)例類(lèi)型
  • isinstance(對(duì)象, 類(lèi)),用來(lái)判斷對(duì)象是不是該類(lèi)的實(shí)例對(duì)象

issubclass:

  • 用于檢查類(lèi)繼承
  • issubclass(類(lèi)1, 類(lèi)2),用來(lái)判斷類(lèi) 1 是否是類(lèi) 2 的子類(lèi)

類(lèi)與數(shù)據(jù)類(lèi)型

Python 與其他編程語(yǔ)言不同,當(dāng)我們定義一個(gè) class 的時(shí)候,我們實(shí)際上就定義了一個(gè)數(shù)據(jù)類(lèi)型。我們定義的數(shù)據(jù)類(lèi)型和 Python 自帶的數(shù)據(jù)類(lèi)型,比如 str、list、dict 沒(méi)什么兩樣:

print(isinstance(10, int))

輸出的結(jié)果為: True

重寫(xiě)父類(lèi)方法

如果父類(lèi)中的方法在子類(lèi)中不適用,我們可以對(duì)其進(jìn)行重寫(xiě):

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def bark(self):
        print('叫')

class Dog(Animal):
    def bark(self):    # 重寫(xiě)叫的方法
        print('汪汪汪!')
    def guarding(self):
        print('看家')
  
wangwang = Dog('汪汪', 3)
print(wangwang.name)
wangwang.bark()

輸出的結(jié)果為:

汪汪
汪汪汪!

重寫(xiě)父類(lèi)方法的原理是,當(dāng)示例調(diào)用方法時(shí),會(huì)先在自己的類(lèi)方法中查找,如果找不到,才會(huì)去父類(lèi)中查找是否有相應(yīng)的方法。如果在自己的類(lèi)方法中找到了需要的方法,就不會(huì)去父類(lèi)中查找,也就調(diào)用不到父類(lèi)的同名方法,從而實(shí)現(xiàn)對(duì)父類(lèi)中方法的重寫(xiě)

調(diào)用父類(lèi)方法

但是有些時(shí)候,我們不得已會(huì)寫(xiě)一些重名的方法,比如父類(lèi)和子類(lèi)都會(huì)有 __init__ 構(gòu)造方法。但是我們?cè)谡{(diào)用子類(lèi)方法的同時(shí),也希望調(diào)用到父類(lèi)中相應(yīng)的方法。我們可以通過(guò)父類(lèi)的類(lèi)名直接調(diào)用:

class Father:
    eye_num = 2
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def live_like_yemen(self):
        print('打兒子')
class Son(Father):
    hair_color = '藍(lán)色'
    def __init__(self, name, age, sex):
        Father.__init__(self, name, age)
        self.sex = sex
    def live_like_yemen(self):
        print('打弟弟')

xiaoming = Son('小明', 16, '男')
xiaoming.live_like_yemen()
print(xiaoming.name)

輸出的結(jié)果為:

打弟弟
小明

需要注意的是,在類(lèi)中,self 永遠(yuǎn)指的是調(diào)用類(lèi)的實(shí)例化對(duì)象。

super 方法

在上面的例子中,如果沒(méi)有 Father.__init__(self, name, age) 這行代碼,在子類(lèi)中就無(wú)法調(diào)用父類(lèi)的構(gòu)造方法,因?yàn)樽宇?lèi)已經(jīng)重寫(xiě)了構(gòu)造方法。上面的方法雖然實(shí)現(xiàn)了預(yù)期的功能,但是并不符合開(kāi)發(fā)規(guī)范。

從子類(lèi)中,調(diào)用父類(lèi)中方法的關(guān)鍵字是 super,上述例子可修改為:

class Father:
    eye_num = 2
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def live_like_yemen(self):
        print('打兒子')
class Son(Father):
    hair_color = '藍(lán)色'
    def __init__(self, name, age, sex):
        super().__init__(name, age)    # 也可以寫(xiě)為super(Son, self).__init__(name, age)
        self.sex = sex
    def live_like_yemen(self):
        print('打弟弟')

xiaoming = Son('小明', 16, '男')
xiaoming.live_like_yemen()
print(xiaoming.name)

super 方法:

  • 子類(lèi)如果編寫(xiě)了自己的構(gòu)造方法,但是沒(méi)有聲明要調(diào)用父類(lèi)的構(gòu)造方法,而還需要父類(lèi)的構(gòu)造函數(shù)中初始化的一些屬性,就會(huì)出現(xiàn)問(wèn)題
  • 如果子類(lèi)和父類(lèi)都有構(gòu)造函數(shù),子類(lèi)的構(gòu)造函數(shù)其實(shí)是對(duì)父類(lèi)的構(gòu)造函數(shù)的重寫(xiě)。如果不顯示調(diào)用父類(lèi)構(gòu)造函數(shù),父類(lèi)的構(gòu)造函數(shù)便不會(huì)被執(zhí)行
  • 解決方法:直接使用超類(lèi)的類(lèi)名調(diào)用超類(lèi)構(gòu)造方法,或者使用 super 函數(shù) super(當(dāng)前類(lèi)名, self).__init__()

父類(lèi)方法重寫(xiě):

  • 子類(lèi)可以重寫(xiě)父類(lèi)中的方法
  • 通過(guò) super 關(guān)鍵字可以調(diào)用父類(lèi)中的方法

Python 中的多繼承

多重繼承和多繼承

多重繼承:包含多個(gè)間接父類(lèi)

class A(object): pass
class B(A): pass
class C(B): pass

多繼承:有多個(gè)直接父類(lèi)

class X(object): pass
class Y(object): pass
class Z(object): pass
class M(X, Y, Z): pass

大部分面向?qū)ο蟮木幊陶Z(yǔ)言(除了 C++)都只支持單繼承,而不支持多繼承

  • 多繼承不僅增加了編程的復(fù)雜度,而且很容易導(dǎo)致一些莫名的錯(cuò)誤 ^1

Python 雖然在語(yǔ)法上明確支持多繼承,但通常推薦如果不是很有必要,盡量不要使用多繼承,而是使用單繼承

  • 這樣可以保證編程思路更清晰,而且可以避免很多麻煩

如果多個(gè)直接父類(lèi)中包含了同名的方法

  • 排在前面的父類(lèi)中的方法會(huì) “遮蔽 “排在后面的父類(lèi)中的同名方法
class A:
    def method(self):
        print('A_method')
class B:
    def method(self):
        print('B_method')
class C(A, B):
    pass

c = C()
c.method()

輸出的結(jié)果為:

A_method

鉆石繼承和 MRO

我們剛剛談到,即便不使用 super 方法,直接使用父類(lèi)的類(lèi)名,同樣可以實(shí)現(xiàn)對(duì)父類(lèi)方法的調(diào)用。那為什么更推薦使用 super 方法呢?

這是因?yàn)楫?dāng)涉及到比較復(fù)雜得多繼承關(guān)系,比如鉆石繼承關(guān)系時(shí),會(huì)出現(xiàn)間接父類(lèi)會(huì)被初始化多次的情況。

比如,我們來(lái)看下面這個(gè)鉆石繼承的例子,如果我們使用父類(lèi)的類(lèi)名調(diào)用構(gòu)造方法:

class YeYe:
    def __init__(self):
        print('初始化爺爺類(lèi)')
class QinBa(YeYe):
    def __init__(self):
        print('進(jìn)入化親爸類(lèi)')
        YeYe.__init__(self)
        print('初始化親爸類(lèi)')
class GanDie(YeYe):
    def __init__(self):
        print('進(jìn)入化干爹類(lèi)')
        YeYe.__init__(self)
        print('初始化干爹類(lèi)')
class ErZi(QinBa, GanDie):
    def __init__(self):
        print('進(jìn)入化兒子類(lèi)')
        QinBa.__init__(self)
        GanDie.__init__(self)
        print('初始化兒子類(lèi)')
erzi = ErZi()

我們看到,程序運(yùn)行后,爺爺類(lèi)被初始化了兩次。

這是因?yàn)椋?dāng)創(chuàng)建兒子對(duì)象時(shí),會(huì)執(zhí)行它的構(gòu)造函數(shù)。首先打印的是兒子類(lèi)中初始化方法的代碼,然后執(zhí)行秦霸的構(gòu)造方法。在親爸的構(gòu)造方法中,也是先打印代碼,然后執(zhí)行爺爺?shù)臉?gòu)造方法。執(zhí)行完?duì)敔數(shù)臉?gòu)造方法之后,程序繼續(xù)執(zhí)行親爸中剩余的代碼,然后回到兒子類(lèi)中,執(zhí)行干爹的構(gòu)造方法。在干爹的構(gòu)造方法中,又要調(diào)用爺爺?shù)臉?gòu)造方法。然后打印剩余代碼,直至結(jié)束。

我們看到,第五步和第十步都是要調(diào)用爺爺?shù)臉?gòu)造方法,爺爺類(lèi)被初始化了兩次。這種情況一來(lái)沒(méi)有必要,會(huì)占用很大空間,二來(lái),多次初始化也會(huì)帶來(lái)程序邏輯的混亂。

如果我們改用 super 函數(shù)來(lái)進(jìn)行這樣的操作,就不會(huì)有這些麻煩:

class YeYe:
    def __init__(self):
        print('初始化爺爺類(lèi)')
        
class QinBa(YeYe):
    def __init__(self):
        print('進(jìn)入親爸類(lèi)')
        super().__init__()
        print('初始化親爸類(lèi)')
        
class GanDie(YeYe):
    def __init__(self):
        print('進(jìn)入干爹類(lèi)')
        super().__init__()
        print('初始化干爹類(lèi)')
        
class ErZi(QinBa, GanDie):
    def __init__(self):
        print('進(jìn)入兒子類(lèi)')
        super().__init__()
        print('初始化兒子類(lèi)')
        
erzi = ErZi()

首先,我們發(fā)現(xiàn),在兒子類(lèi)中,我們只用一行代碼指代調(diào)用兩個(gè)直接父類(lèi)的構(gòu)造方法。然后,從結(jié)果上看,此時(shí),爺爺類(lèi)只被初始化一次。

而且我們發(fā)現(xiàn),代碼的運(yùn)行情況與多個(gè)裝飾器裝飾一個(gè)函數(shù)的情況很類(lèi)似,子類(lèi)的代碼包含著父類(lèi)的代碼,一層套一層的形式。


查看 mro 的方法有兩種:

類(lèi)名.mro()
對(duì)象名.__class__.mro()

前面例子中的 mro 為:

[<class '__main__.ErZi'>, <class '__main__.QinBa'>, <class '__main__.GanDie'>, <class '__main__.YeYe'>, <class 'object'>]

我們說(shuō)過(guò),super 后面什么都不寫(xiě),默認(rèn)和 super(當(dāng)前類(lèi)名, self) 的寫(xiě)法一樣。但事實(shí)上,super 的參數(shù)除了可以寫(xiě)當(dāng)前類(lèi)名外,還可以寫(xiě)它的父類(lèi) [^3] 的類(lèi)名。此時(shí),會(huì)執(zhí)行在方法解析順序列表中,該類(lèi)下一個(gè)類(lèi)的方法。

補(bǔ)充了這些知識(shí),我們就可以解釋上面的程序運(yùn)行的順序了。

super 關(guān)鍵字詳解:

  • 基本結(jié)構(gòu):super(class[, object or class])
  • Python 3 可以直接使用 super().xxx 代替 super(class, self).xxx
  • 使用多繼承時(shí),會(huì)涉及到查找順序(MRO)、鉆石繼承等問(wèn)題單繼承時(shí),類(lèi)名.__init__() 的方式和 super().__init__() 的方式調(diào)用父類(lèi)中的方法沒(méi)有什么差別使用 類(lèi)名.__init__() 的方式在鉆石繼承時(shí),會(huì)遇到初始化混亂的問(wèn)題

super 內(nèi)核的 mro 方法:返回的是一個(gè)類(lèi)的方法解析順序表(順序結(jié)構(gòu))

  • 我們定義的每一個(gè)類(lèi),Python 都會(huì)計(jì)算出一個(gè)方法解析順序(MRO [^2])列表這也是 super 在父類(lèi)中查找成員的順序,它是通過(guò) C3 線性算法來(lái)實(shí)現(xiàn)的
  • 每個(gè)父類(lèi) [^3] 都存在且只在其中出現(xiàn)一次
  • 我們可以通過(guò)下面兩種方式獲得某個(gè)類(lèi)的 mro 列表:
類(lèi)名.mro()
對(duì)象名.__class__.mro()
  • 當(dāng)使用 super(cls, obj) 時(shí),Python 會(huì)在 obj 的 mro 列表上搜索 cls 的下一個(gè)類(lèi)

事實(shí)上,super 和父類(lèi)沒(méi)有實(shí)質(zhì)性的關(guān)聯(lián),我們也不一定非要把 super 后面的參數(shù)寫(xiě)成自己類(lèi)的名字和 self。我們甚至可以很靈活地給 super 傳參數(shù)

super(cls, obj) 獲得的是 cls 在 obj 的 MRO 列表中的下一個(gè)類(lèi),cls 可以是任何一個(gè)類(lèi),obj 可以是任何一個(gè)對(duì)象,只要合理即可

class class ErZi(Qinba,GanDie):
    def __init__(self):
        super(ErZi, self).__init__()
        print('初始化兒子')

在前面我們定義兒子類(lèi)的時(shí)候,如果我們不想調(diào)用親爸的 __init__(),而是要調(diào)用干爹的 __init__(),只需把 super 寫(xiě)成 super(Qinba, self).__init__(),也就是這樣:

class YeYe:
    def __init__(self):
        print('初始化爺爺類(lèi)')

class QinBa(YeYe):
    def __init__(self):
        print('進(jìn)入親爸類(lèi)')
        super().__init__()
        print('初始化親爸類(lèi)')

class GanDie(YeYe):
    def __init__(self):
        print('進(jìn)入干爹類(lèi)')
        super().__init__()
        print('初始化干爹類(lèi)')

class ErZi(QinBa, GanDie):
    def __init__(self):
        print('進(jìn)入兒子類(lèi)')
        super(QinBa, self).__init__()
        print('初始化兒子類(lèi)')

erzi = ErZi()

其執(zhí)行順序?yàn)椋?/p>

[^2]: Method Resolution Order

[^3]: 包括直接父類(lèi)和間接父類(lèi)

分享到:
標(biāo)簽:Python
用戶(hù)無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定