面向對象:繼承、派生
繼承:
繼承是指類與類之間的關系,是一種“什麽”是“什麽”的關系。
繼承的功能之一就是用來解決代碼重用問題
繼承是一種創建新類的方式,在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
面向對象:繼承、派生