1. 程式人生 > >面向對象:繼承、派生

面向對象:繼承、派生

可能 定義類 div dem object類 bject b- strong pan

繼承:

繼承是指類與類之間的關系,是一種“什麽”是“什麽”的關系。

繼承的功能之一就是用來解決代碼重用問題

繼承是一種創建新類的方式,在Python中,新建的類可以繼承一個或多個父類,父類又可以稱為基類或者超類,新建的類稱為派生類或者子類

如下代碼所示:

class ParentClass1:
    pass

class ParentClass2:
    pass

class SubClass1(ParentClass1):  # SubClass1 是 ParentClass1的子類,ParentClass1是SubClass1的父類(基類)
    pass

class Subclass2(ParentClass1,ParentClass2):  #
SubClass繼承了 ParentClass1和ParentClass1兩個父類 pass
# 查看繼承 """ 可利用 .__bases__ 的方式查看其基類(父類)(元祖的形式) """ print(SubClass1.__bases__) print(Subclass2.__bases__) # 打印結果: # (<class ‘__main__.ParentClass1‘>,) # (<class ‘__main__.ParentClass1‘>, <class ‘__main__.ParentClass2‘>)

抽象與繼承(先抽象後繼承):

抽象:即抽取類似或者說比較像的部分; 抽象最主要的作用是劃分類別

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

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

如上一節創建兩個英雄類的例子:

class Garen:
    camp = "Demacia"

    def __init__(self,nickname,attack,life):
        self.nickname = nickname
        self.attack = attack
        self.life 
= life def attacking(self,enemy): enemy.life -= self.attack class Riven: camp = "Noxus" def __init__(self,nickname,attack,life): self.nickname = nickname self.attack = attack self.life = life def attacking(self,enemy): enemy.life -= self.attack # Garen類是英雄類, Riven類也是英雄類, 所以可以把Garen和Riven提取出一個Hero類 # 如下代碼所示

對上述代碼稍作修改後並提取父類,如下:

class Hero:
    def __init__(self,nickname,attack,life):
        self.nickname = nickname
        self.attack = attack
        self.life = life

    def attacking(self,enemy):
        enemy.life -= self.attack


class Garen(Hero):
    pass


class Riven(Hero):
    pass

garen = Garen("草叢倫",30,100)
print(garen.nickname,garen.attack,garen.life)   
""" 雖然Garen這個類下面沒有代碼,更沒有__init__函數,但是Garen 繼承了Hero這個類的屬性 """  
"""關於屬性查找優先順序,以 garen.nickname 為例,它會先去garen的命名空間裏面去找有沒有 nickname,如果沒有再去garen的父類(Garen類)裏面去找,如果還沒有,再去其父類的父類(Hero類)去找...,如果最後都就沒找到就報錯"""
print(garen.__dict__)
# 運行結果: 
#
草叢倫 30 100
#
{‘nickname‘: ‘草叢倫‘, ‘attack‘: 30, ‘life‘: 100}


所以,通過繼承,子類能夠重用父類的屬性。

屬性查找順序:

# 情況1:
class Foo:
    def f1(self):
        print("from Foo.f1")

    def f2(self):
        print("from Foo.f2")
        self.f1()

class Bar(Foo):
    def f2(self):
        print("from Bar.f2")

b = Bar()
b.f2()   #  調用f2時,首先會去b的命名空間裏面找,但是,Bar中都沒有__init__函數,所以b裏面肯定為空;b裏面沒有就繼續往Bar裏面找
print(b.__dict__)

# 打印結果:
# from Bar.f2
# {}


# 情況2:
class Foo:
    def f1(self):
        print("from Foo.f1")

    def f2(self):
        print("from Foo.f2")
        self.f1()  # 哪個對象調用f2,self就是誰;在這個例子中self代表b這個對象,所以這句代碼代表的含義為: b.f1()

class Bar(Foo):
    def f1(self):
        print("from Bar.f2")

b = Bar()
b.f2()   # 同理,程序會現在b的命名空間裏面查找、調用f2;沒找到再往Bar裏面去找;也沒找到就往Foo裏面去找;在Foo中找到後執行f2,此時self.f1()變成了b.f1(),b.f1()在執行時也是先在b裏面找,然後在Bar裏面找,所以self.f1()執行的是Bar裏面的f1,而不是Foo中的


# 執行結果:
# from Foo.f2
# from Bar.f2

派生:

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

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

“指名道姓”式的調用(不依賴繼承關系)

class Hero:
    def __init__(self,nickname,attack,life):
        self.nickname = nickname
        self.attack = attack
        self.life = life

    def attacking(self,enemy):
        enemy.life -= self.attack


class Riven(Hero):
    camp=Noxus
    def __init__(self,nickname,attack,life,skin):
        Hero.__init__(self,nickname,attack,life) #為了減少重復代碼,調用父類的__init__功能  # “指名道姓”式的調用,不依賴繼承,同理需要寫self
        self.skin=skin #新屬性
    def attacking(self,enemy): #在自己這裏定義新的attack,不再使用父類的attack,且不會影響父類
        Hero.attack(self,enemy) #調用父類Hero中的attack功能  # 這是“指名道姓”式的調用,這種調用不依賴於繼承關系,即:沒有繼承關系也能調用 # 因為沒有根據繼承關系調用,且調用的是其他類(Hero類)中的函數,因為類在調用函數的時候需要傳入self這個參數(即對象本身),所以應該寫成 Hero.attack(self,enemy),即需要傳self
        print(from riven)
    def fly(self): #在自己這裏定義新的
        print(%s is flying %self.nickname)

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

print(r1.skin)

# 執行結果:
# {‘nickname‘: ‘銳雯雯‘, ‘attack‘: 57, ‘life‘: 200, ‘skin‘: ‘比基尼‘}
#
銳雯雯 is flying
#
比基尼

繼承的實現原理:

經典類和新式類:

  1. 只有Python2中才分經典類和新式類,Python3中統一都是新式類

  2. 在Python2中,沒有繼承object類的類,以及該類的子類,都是經典類

  3. 在Python2中,繼承object類的類,以及該類的子類,都是新式類

  4. 在Python3中,無論是否聲明繼承object,都默認繼承object,即Python3中所有類均為新式類

(object類定義了所有類所共有的一些內置方法)

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

繼承的實現:對於你定義的每一個類,Python會計算出一個方法解析順序(MRO),這個MRO(元祖、列表)就是一個簡單的所有基類的線性順序列表(MRO全稱 method resolution order)

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

MRO列表合並了所有的父類並遵守如下規則:

  1. 對象優先於類被查找,子類會優先於父類被查找

  2. 多個父類會根據他們在列表中的位置順序被查找

  3. 如果對下一個類存在兩個合法的選擇,則選擇第一個父類

Python中子類可以同時繼承多個父類;如果繼承了多個父類,那麽屬性的查找方式有兩種:深度優先(經典類)和廣度優先(新式類)

深度優先:

技術分享圖片

廣度優先:

技術分享圖片

示例代碼:

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__) # .mro() :只有新式類才有這個屬性可以查看線性列表,經典類沒有這個屬性

# 執行結果:
# from D
# (<class ‘__main__.F‘>, <class ‘__main__.D‘>, <class ‘__main__.B‘>, <class ‘__main__.E‘>, <class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘object‘>)

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

在子類中重用父類的方法或屬性,有兩種方法:

  1. “指名道姓”式的調用,不依賴繼承關系(此方式上面的代碼已經寫過)

  2. 利用super()方式,依賴繼承關系

super()方式:

class Hero:
    def __init__(self,nickname,attack,life):
        self.nickname = nickname
        self.attack = attack
        self.life = life

    def attacking(self,enemy):
        enemy.life -= self.attack


class Riven(Hero):

    camp = "Noxus"
    def __init__(self,nickname,attack,life,skin):
        super(Riven,self).__init__(nickname,attack,life)  # super()方法,依賴繼承關系 # .__init__函數無需再寫self參數

        # 在Python3中可以簡寫為:
        # super().__init__(nickname,attack,life)

        self.skin = skin

    def attacking(self,enemy):
        super(Riven,self).attacking(enemy)  # 這是super()方法,此方法依賴於繼承關系  # super(Riven,self): super中的第一個參數傳子類自己的類名,第二個寫成self, 這樣能夠得到一個特殊的對象(其父類Hero的一個對象),這個特殊的對象能夠調用Hero類中的屬性;super(Riven,self).attacking(enemy): .attacking(enemy)就是前面得到的那個父類的特殊對象在調用Hero中的attacking函數,因為是對象在調用函數,所以不需要再在函數裏面傳入self。(或者說不是在調用函數,而是在調用綁定方法)

        """
        super(Riven,self)是Python2的寫法,在Python3中可以簡寫為:
        super().attacking(enemy)
        """

        print(from riven)
    def fly(self):
        print(%s is flying %self.nickname)

class Garen(Hero):
    camp = "Demacia"


r1 = Riven(銳雯雯, 50,80,"比基尼")
print(r1.__dict__)
g1 = Garen("草叢倫",30,100)

r1.attacking(g1)
print(g1.life)

# 運行結果:
# {‘nickname‘: ‘銳雯雯‘, ‘attack‘: 50, ‘life‘: 80, ‘skin‘: ‘比基尼‘}
# from riven
# 50

super()的運行機制:

  # super()只會沿著基於某個類產生的MRO列表,依次往後尋找

代碼示例:

class A:
    def f1(self):
        print("form A")
        super().f1()

class B:
    def f1(self):
        print("from B")

class C(A,B):
    pass

# C.mro()的結果:
# [<class ‘__main__.C‘>,
# <class ‘__main__.A‘>,
# <class ‘__main__.B‘>,
# <class ‘object‘>]

c = C()
c.f1()

"""
運行過程分析:c = C()會生成一個基於C類的MRO列表,super()會根據這個基於C類繼承關系產生的MRO列表依次查找需要調用的屬性(so super()是基於繼承關系的,因為繼承關系能夠產生一個有順序的MRO列表)
 具體過程: 先在c這個對象中找有沒有f1(),再往C這個類中找有沒有f1();然後再往A中去找,找到了f1(),執行A的f1()後先打印“from A”,
然後,super().f1():由於此時程序scan MRO列表已經剛剛找完了<class ‘__main__.A‘>,super()會接著這個基於C產生的MRO列表繼續往後尋找f1()
然後再B中找到了f1(),執行B中的f1()
所以,雖然A沒有繼承B,但是super()只會沿著基於C的繼承關系產生的MRO列表按順序往後查詢;即 super()不看它們的繼承關系,只看基於誰產生的MRO列表順序。即:只會參照C的MRO列表
"""


# 運行結果:
# form A
# from B

面向對象:繼承、派生