1. 程式人生 > >lesson 042 —— 繼承

lesson 042 —— 繼承

要去 分支 -i 具體實現 是否 lease 外部 調用 部分

lesson 042 —— 繼承

什麽是繼承?

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

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

class ParentClass1: #定義父類
    pass

class ParentClass2: #定義父類
    pass

class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
    pass

class SubClass2(ParentClass1, ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類
    pass

查看繼承

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

註意:

  1. 只有在python2中才分新式類和經典類,python3中統一都是新式類
  2. 在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類;顯式地聲明繼承object的類,以及該類的子類,都是新式類
  3. python3中,無論是否繼承object,都默認繼承object,即python3中所有類均為新式類

如果沒有指定基類,python的類會默認繼承 object 類,object 是所有python類的基類,它提供了一些常見方法(如__str__)的實現。

>>> ParentClass1.__bases__
(<class ‘object‘>,)
>>> ParentClass2.__bases__
(<class ‘object‘>,)

繼承與抽象

繼承描述的是子類與父類之間的關系,是一種什麽是什麽的關系。要找出這種關系,必須先抽象再繼承。

抽象即抽取類似或者說比較像的部分。抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類。

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

繼承與重用性

在開發程序的過程中,如果我們定義了一個類A,然後又想新建立另外一個類B,但是類B的大部分內容與類A的相同時,我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。 通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數據屬性和函數屬性),實現代碼重用。

派生

當然子類也可以添加自己新的屬性或者在自己這裏重新定義這些屬性(不會影響到父類),需要註意的是,一旦重新定義了自己的屬性且與父類重名,那麽調用新增的屬性時,就以自己為準了。

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

class Hero:
    def __init__(self,nickname,aggressivity,life_value):
        self.nickname=nickname
        self.aggressivity=aggressivity
        self.life_value=life_value

    def move_forward(self):
        print(‘%s move forward‘ %self.nickname)

    def move_backward(self):
        print(‘%s move backward‘ %self.nickname)

    def move_left(self):
        print(‘%s move forward‘ %self.nickname)

    def move_right(self):
        print(‘%s move forward‘ %self.nickname)

    def attack(self,enemy):
        enemy.life_value-=self.aggressivity
        
class Riven(Hero):
    camp=‘Noxus‘
    def __init__(self,nickname,aggressivity,life_value,skin):
        Hero.__init__(self,nickname,aggressivity,life_value) #調用父類功能
        self.skin=skin #新屬性
    def attack(self,enemy): #在自己這裏定義新的attack,不再使用父類的attack,且不會影響父類
        Hero.attack(self,enemy) #調用功能,傳入self參數,即這裏使用self這個對象
        print(‘from riven‘)
    def fly(self): #在自己這裏定義新的
        print(‘%s is flying‘ %self.nickname)

r1=Riven(‘銳雯雯‘,57,200,‘比基尼‘)
r1.fly()
print(r1.skin)

‘‘‘
運行結果
銳雯雯 is flying
比基尼
‘‘‘

組合

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

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

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

  1. 繼承的方式

    通過繼承建立了派生類與基類之間的關系,它是一種‘是‘的關系,比如白馬是馬,人是動物。當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好.

  2. 組合的方式

    用組合的方式建立了類與組合的類之間的關系,它是一種‘有’的關系,比如教授有生日,教授教python和linux課程,教授有學生s1、s2、s3...當類之間有顯著不同,並且較小的類是較大的類所需要的組件時,用組合比較好.

接口與歸一化設計

1. 接口的特性
  • 是一組功能的集合,而不是一個功能
  • 接口的功能用於交互,所有的功能都是public,即別的對象可操作
  • 接口只定義函數,但不涉及函數實現
  • 這些功能是相關的
2. 為什麽用接口

接口提取了一群類共同的函數,可以把接口當做一個函數的集合。然後讓子類去實現接口中的函數。這麽做的意義在於歸一化,什麽叫歸一化,就是只要是基於同一個接口實現的類,那麽所有的這些類產生的對象在使用時,從用法上來說都一樣。

歸一化的好處:

  • 歸一化讓使用者無需關心對象的類是什麽,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
  • 歸一化使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合。就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存、磁盤、網絡還是屏幕(當然,對底層設計者,當然也可以區分出“字符設備”和“塊設備”,然後做出針對性的設計:細致到什麽程度,視需求而定)。
3. 模仿 interface

在python中根本就沒有一個叫做interface的關鍵字,如果非要去模仿接口的概念,可以借助第三方模塊:

zope.interface,可以參考文檔和設計模式。

也可以使用繼承。繼承的兩種用途:

  1. 繼承基類的方法,並且做出自己的改變或者擴展(代碼重用):實踐中,繼承的這種用途意義並不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。
  2. 聲明某個子類兼容於某基類,定義一個接口類(模仿java的Interface),接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,並且實現接口中的功能。
class Interface:#定義接口Interface類來模仿接口的概念,python中壓根就沒有interface關鍵字來定義一個接口。
    def read(self): #定接口函數read
        pass

    def write(self): #定義接口函數write
        pass

class Txt(Interface): #文本,具體實現read和write
    def read(self):
        print(‘文本數據的讀取方法‘)

    def write(self):
        print(‘文本數據的讀取方法‘)

class Sata(Interface): #磁盤,具體實現read和write
    def read(self):
        print(‘硬盤數據的讀取方法‘)

    def write(self):
        print(‘硬盤數據的讀取方法‘)

class Process(Interface):
    def read(self):
        print(‘進程數據的讀取方法‘)

    def write(self):
        print(‘進程數據的讀取方法‘)

上面的代碼只是看起來像接口,其實並沒有起到接口的作用,子類完全可以不用去實現接口 ,這就用到了抽象類。

抽象類

1. 什麽是抽象類?

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

2. 為什麽要有抽象類

如果說類是從一堆對象中抽取相同的內容而來的,那麽抽象類就是從一堆類中抽取相同的內容而來的,內容包括數據屬性和函數屬性。

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

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

3. Python 中實現抽象類
#一切皆文件
import abc #利用abc模塊實現抽象類

class All_file(metaclass=abc.ABCMeta):
    all_type=‘file‘
    @abc.abstractmethod #定義抽象方法,無需實現功能
    def read(self):
        ‘子類必須定義讀功能‘
        pass

    @abc.abstractmethod #定義抽象方法,無需實現功能
    def write(self):
        ‘子類必須定義寫功能‘
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #報錯,子類沒有定義抽象方法

class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法
    def read(self):
        print(‘文本數據的讀取方法‘)

    def write(self):
        print(‘文本數據的讀取方法‘)

class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法
    def read(self):
        print(‘硬盤數據的讀取方法‘)

    def write(self):
        print(‘硬盤數據的讀取方法‘)

class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法
    def read(self):
        print(‘進程數據的讀取方法‘)

    def write(self):
        print(‘進程數據的讀取方法‘)

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#這樣大家都是被歸一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
4. 抽象類與接口

抽象類的本質還是類,指的是一組類的相似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的相似性。

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

繼承實現的原理

1. 繼承順序

在Java和C#中子類只能繼承一個父類,而Python中子類可以同時繼承多個父類,如A(B,C,D)。

如果繼承關系為非菱形結構,則會按照先找B這一條分支,然後再找C這一條分支,最後找D這一條分支的順序直到找到我們想要的屬性。

如果繼承關系為菱形結構,那麽屬性的查找方式有兩種,分別是:深度優先和廣度優先。

技術分享圖片

技術分享圖片

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會計算出一個方法解析順序(MRO)列表,這個 MRO(method resolution order) 列表就是一個簡單的所有基類的線性順序列表,例如:

>>> 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列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。

而這個 MRO 列表的構造是通過一個 C3線性化算法來實現的。我們不去深究這個算法的數學原理,它實際上就是合並所有父類的 MRO 列表並遵循如下三條準則:

  1. 子類會先於父類被檢查
  2. 多個父類會根據它們在列表中的順序被檢查
  3. 如果對下一個類存在兩個合法的選擇,選擇第一個父類

子類調用父類的方法

方法一:指名道姓,即 父類名.父類方法()
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()
方法二: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()

強調: 二者使用哪一種都可以,但最好不要混用。

即使沒有直接繼承關系,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‘>]

兩種調用父類方法的區別:

當你使用 super() 函數時,Python會在MRO列表上繼續搜索下一個類。只要每個重定義的方法統一使用super()並只調用它一次,那麽控制流最終會遍歷完整個MRO列表,每個方法也只會被調用一次(註意註意註意:使用super調用的所有屬性,都是從MRO列表當前的位置往後找,千萬不要通過看代碼去找繼承關系,一定要看MRO列表


#指名道姓
class A:
    def __init__(self):
        print(‘A的構造方法‘)
class B(A):
    def __init__(self):
        print(‘B的構造方法‘)
        A.__init__(self)


class C(A):
    def __init__(self):
        print(‘C的構造方法‘)
        A.__init__(self)


class D(B,C):
    def __init__(self):
        print(‘D的構造方法‘)
        B.__init__(self)
        C.__init__(self)

    pass
f1=D() #A.__init__被重復調用
‘‘‘
D的構造方法
B的構造方法
A的構造方法
C的構造方法
A的構造方法
‘‘‘


#使用super()
class A:
    def __init__(self):
        print(‘A的構造方法‘)
class B(A):
    def __init__(self):
        print(‘B的構造方法‘)
        super(B,self).__init__()


class C(A):
    def __init__(self):
        print(‘C的構造方法‘)
        super(C,self).__init__()


class D(B,C):
    def __init__(self):
        print(‘D的構造方法‘)
        super(D,self).__init__()

f1=D() #super()會基於mro列表,往後找
‘‘‘
D的構造方法
B的構造方法
C的構造方法
A的構造方法
‘‘‘

lesson 042 —— 繼承