1. 程式人生 > >面向物件的繼承和派生

面向物件的繼承和派生

一、繼承

1、什麼是繼承

繼承是一種建立新類的方式,新建的類可以繼承一個或多個父類(python支援多繼承),父類又可稱為基類或超類,新建的類稱為派生類或子類。

子類會“”遺傳”父類的屬性,從而解決程式碼重用問題(比如練習7中Garen與Riven類有很多冗餘的程式碼)

2、python中類的繼承分為:單繼承和多繼承

class ParentClass1: #定義父類
    pass

class ParentClass2: #定義父類
    pass

class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
pass class SubClass2(ParentClass1,ParentClass2): #python支援多繼承,用逗號分隔開多個繼承的類 pass
View Code

3、檢視繼承

>>> SubClass1.__bases__ #__base__只檢視從左到右繼承的第一個子類,__bases__則是檢視所有繼承的父類
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1
'>, <class '__main__.ParentClass2'>)
View Code

4、經典類與新式類

1.只有在python2中才分新式類和經典類,python3中統一都是新式類
2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類
3.在python2中,顯式地宣告繼承object的類,以及該類的子類,都是新式類
3.在python3中,無論是否繼承object,都預設繼承object,即python3中所有類均為新式類
#關於新式類與經典類的區別,我們稍後討論
View Code

二、派生

當然子類也可以新增自己新的屬性或者在自己這裡重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那麼呼叫新增的屬性時,就以自己為準了。

class Riven(Hero):
    camp='Noxus'
    def attack(self,enemy): #在自己這裡定義新的attack,不再使用父類的attack,且不會影響父類
        print('from riven')
    def fly(self): #在自己這裡定義新的
        print('%s is flying' %self.nickname)

三、組合與重用性

組合:類與類之間沒有關聯,但又想把它們組合到一起

>>> class Equip: #武器裝備類
...     def fire(self):
...         print('release Fire skill')
... 
>>> class Riven: #英雄Riven的類,一個英雄需要有裝備,因而需要組合Equip類
...     camp='Noxus'
...     def __init__(self,nickname):
...         self.nickname=nickname
...         self.equip=Equip() #用Equip類產生一個裝備,賦值給例項的equip屬性
... 
>>> r1=Riven('銳雯雯')
>>> r1.equip.fire() #可以使用組合的類產生的物件所持有的方法
release Fire skill
View Code

組合與繼承都是有效地利用已有類的資源的重要方式。但是二者的概念和使用場景皆不同

1.繼承的方式

通過繼承建立了派生類與基類之間的關係,它是一種'是'的關係,比如白馬是馬,人是動物。

當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如老師是人,學生是人

2.組合的方式

用組合的方式建立了類與組合的類之間的關係,它是一種‘有’的關係,比如教授有生日,教授教python和linux課程,教授有學生s1、s2、s3...

當類之間有顯著不同,並且較小的類是較大的類所需要的元件時,用組合比較好

四、介面與歸一化設計

介面提取了一群類共同的函式,可以把介面當做一個函式的集合。然後讓子類去實現介面中的函式。

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

例如我們定義一個硬碟 ALLFile 的類,其他子類可以通過繼承它實現具體的功能。但是如果我們只是單純的寫成下面這樣:

class AllFile():
    def read(self):
        pass

    def write(self):
        pass


class Disk(AllFile):
    def read(self):
        print("read Disk")

disk = Disk()

這樣雖然我們可以繼承,但我們不用必須去實現父類所有的方法,普通的繼承達不到一種我們想要的約束的作用,這時改成

import abc

class
AllFile(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass class Disk(AllFile): def read(self): print("read Disk") def write(self): print("write Disk") disk = Disk()

注意,父類只提供介面,具體實現邏輯在子類裡面

歸一化的好處在於:

1. 歸一化讓使用者無需關心物件的類是什麼,只需要的知道這些物件都具備某些功能就可以了,這極大地降低了使用者的使用難度。

2. 歸一化使得高層的外部使用者可以不加區分的處理所有介面相容的物件集合

五、繼承實現的原理

1、子類查詢父類的順序

分別是:深度優先和廣度優先

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中才分新式類與經典類

繼承順序
繼承順序

2、繼承原理(python如何實現的繼承)

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

>>> F.mro() #等同於F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

為了實現繼承,python會在MRO列表上從左到右開始查詢基類,直到找到第一個匹配這個屬性的類為止。

六、子類中呼叫父類的方法

方法一:即父類名.父類方法()

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'

class Vehicle: #定義交通工具類
     Country='China'
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print('開動啦...')

class Subway(Vehicle): #地鐵
    def __init__(self,name,speed,load,power,line):
        Vehicle.__init__(self,name,speed,load,power)
        self.line=line

    def run(self):
        print('地鐵%s號線歡迎您' %self.line)
        Vehicle.run(self)

line13=Subway('中國地鐵','180m/s','1000人/箱','',13)
line13.run()
View Code

方法二:super()

class Vehicle: #定義交通工具類
     Country='China'
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print('開動啦...')

class Subway(Vehicle): #地鐵
    def __init__(self,name,speed,load,power,line):
        #super(Subway,self) 就相當於例項本身 在python3中super()等同於super(Subway,self)
        super().__init__(name,speed,load,power)
        self.line=line

    def run(self):
        print('地鐵%s號線歡迎您' %self.line)
        super(Subway,self).run()

class Mobike(Vehicle):#摩拜單車
    pass

line13=Subway('中國地鐵','180m/s','1000人/箱','',13)
line13.run()
View Code

注意:super()呼叫的時候不用傳self

注意:即使沒有直接繼承關係,super仍然會按照mro繼續往後查詢

#A沒有繼承B,但是A內super會基於C.mro()繼續往後找
class A:
    def test(self):
        super().test()
class B:
    def test(self):
        print('from B')
class C(A,B):
    pass

c=C()
c.test() #列印結果:from B


print(C.mro())
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
View Code