python語法學習之super(),繼承與派生
1 什麼是繼承?
繼承是一種創建新類的方式;
在Python中,新建的類可以繼承一個或多個父類,新建的類可稱為子類或派生類,父類又可稱為基類或超類。
繼承可以用來解決類與類之間的代碼重用性問題;
class ParentClass1: #定義父類 pass class ParentClass2: #定義父類 pass class SubClass1(ParentClass1): #單繼承 pass class SubClass2(ParentClass1,ParentClass2): #多繼承 pass
註意:
在python中一個子類可以繼承多個父類,在其他語言中,一個子類隻能繼承一個父類;
python中的繼承分為單繼承和多繼承;
通過類的內置屬性__bases__可以查看類繼承的所有父類
>>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
在Python3中隻有新式類,即使沒有顯式地繼承object,也會默認繼承該類。
2 繼承的規則
子類繼承父類的成員變量和成員方法:
- 子類不繼承父類的構造方法,能夠繼承父類的析構方法
- 子類不能刪除父類的成員,但可以重定義父類成員
- 子類可以增加自己的成員
示例:
# python中子類繼承父類成員變量之間的取值邏輯 class Person(): def __init__(self, name, age, sex): self.name = "jasn" self.age = '18' self.sex = sex def talk(self): print("i want to speak something to yo!!") class Chinese(Person): def __init__(self, name, age, sex, language): Person.__init__(self, name, age, sex) # 用父類的name,age,sex 覆蓋掉子類的屬性 self.age = age # 覆蓋掉瞭父類的age,取值為子類實例中傳入age參數 self.language = "chinese" def talk(self): print("我說的是普通話!!") Person.talk(self) obj = Chinese("nancy",'18','male',"普通話") print(obj.name) # 對應場景A print(obj.age) # 對應場景B print(obj.language) # 對應場景C obj.talk() # 對應場景D # 總結: # A:若父類中初始化瞭成員變量,子類調用父類構造方法未覆蓋屬性(self.name),則調用子類屬性時取值為父類中初始化的成員變量; # B:若父類中初始化瞭成員變量,若子類調用父類構造方法覆蓋屬性(self.age)則取值為子類實例中傳入參數 # C:若父類未初始化該成員變量,則無論子類中有無進行對父類構造方法進行屬性的覆蓋,均取子類實例中傳入的參數 # D:對於方法,如果子類有這個方法則直接調用,如果子類沒有則去父類查找。父類沒有則報錯
3 繼承原理
對於你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表。
>>> D.mro() # 新式類內置瞭mro方法可以查看線性列表的內容,經典類沒有該內置方法 [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止,而這個MRO列表的構造是通過一個C3線性化算法來實現的。
實際上就是合並所有父類的MRO列表並遵循如下三條準則:
- 子類會先於父類被檢查
- 多個父類會根據它們在列表中的順序被檢查
- 如果對下一個類存在兩個合法的選擇,選擇第一個父
Python 中的 MRO —— 方法搜索順序
Python中針對類提供瞭一個內置屬性 mro 可以查看方法搜索順序
MRO 是 method resolution order,主要用於在多繼承時判斷方法、屬性 的調用路徑。
print(C.__mro__) #C是多繼承後的類名
輸出結果:
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
在搜索方法時,是按照 mro 的輸出結果 從左至右 的順序查找的;
如果在當前類中找到方法,就直接執行,不再搜索;如果沒有找到,就查找下一個類中是否有對應的方法,如果找到,就直接執行,不再搜索。
如果找到最後一個類,還沒有找到方法,程序報錯。
4 多繼承屬性查詢順序
(1)多繼承結構為菱形結構
如果繼承關系為菱形結構,那麼經典類與新式類會有不同MRO。
箭頭表示搜索順序
(2)多繼承結構為非菱形結構
會按照先找B這一條分支,然後再找C這一條分支,最後找D這一條分支的順序直到找到我們想要的屬性。
5 查找流程
① 由對象發起的屬性查找,會從對象自身的屬性裡檢索,沒有則會按照對象的類.mro()規定的順序依次找下去。
② 由類發起的屬性查找,會按照當前類.mro()規定的順序依次找下去。
主要知識點:類的__mro__ 屬性的用法;
屬性查找
有瞭繼承關系,對象在查找屬性時,先從對象自己的__dict__中找,如果沒有則去子類中找,然後再去父類中找…..
class Foo: def f1(self): print('Foo.f1') def f2(self): print('Foo.f2') self.f1() class Bar(Foo): def f1(self): print('Bar.f1') b=Bar() b.f2() # 運行結果: Foo.f2 Bar.f1 # 運行流程分析: b.f2()會在父類Foo中找到f2,先打印Foo.f2,然後執行到self.f1(),即b.f1(),仍會按照:對象本身->類Bar->父類Foo的順序依次找下去,在類Bar中找到f1,因而打印結果為Bar.f1
父類如果不想讓子類覆蓋自己的方法,可以采用雙下劃線開頭的方式將方法設置為私有的:
class Foo: def __f1(self): # 變形為_Foo__f1 print('Foo.f1') def f2(self): print('Foo.f2') self.__f1() # 變形為self._Foo__f1,然後回到Bar類中找,沒有,再到Foo中找到瞭 class Bar(Foo): def __f1(self): # 變形為_Bar__f1 print('Bar.f1') b=Bar() # Bar類處於執行階段,_Bar__f1變為__f1 b.f2() # 在父類中找到f2方法,進而調用b._Foo__f1()方法,是在父類中找到的f1方法 # 運行結果: Foo.f2 Foo.f1
6 繼承概念的實現
方式主要有2類:
- 實現繼承
- 接口繼承
① 實現繼承是指使用基類的屬性和方法而無需額外編碼的能力。
② 接口繼承是指僅使用屬性和方法的名稱、但是子類必須提供實現的能力(子類重構爹類方法)。
在考慮使用繼承時,有一點需要註意,那就是兩個類之間的關系應該是“屬於”關系。
例如:Employee 是一個人,Manager 也是一個人,因此這兩個類都可以繼承 Person 類,但是 Leg 類卻不能繼承 Person 類,因為腿並不是一個人。
7 私有屬性私有方法在繼承中的表現
父類中的私有方法和私有屬性都是不能被子類繼承下來的;
測試示例:父類中的私有屬性和私有方法是否能被繼承下來?
class Perpon: num = 20 __num1 = 12 def __test1(self): print('__test1....') def test2(self): print('test2...') class Student(Perpon): def test(self): print('num...') print(self.num) # print(Student.__num1) self.test2() # self.__test1() student = Student() student.test() student.test2() # student.__test1() # 報錯 ''' num... 20 test2... test2... '''
8 派生類
1)在父類的基礎上產生子類,產生的子類就叫做派生類
2)父類裡沒有的方法,在子類中有瞭,這樣的方法就叫做派生方法。
3)父類裡有,子類也有的方法,就叫做方法的重寫(就是把父類裡的方法重寫瞭)
class Hero: def __init__(self, nickname,aggressivity,life_value): self.nickname = nickname self.aggressivity = aggressivity self.life_value = life_value def attack(self, enemy): print('Hero attack') class Garen(Hero): camp = 'Demacia' def attack(self, enemy): #self=g1,enemy=r1 # self.attack(enemy) #g1.attack(r1),這裡相當於無限遞歸 Hero.attack(self,enemy) # 引用 父類的 attack,對象會去跑 父類的 attack print('from garen attack') # 再回來這裡 def fire(self): print('%s is firing' % self.nickname) class Riven(Hero): camp = 'Noxus' g1 = Garen('garen', 18, 200) r1 = Riven('rivren', 18, 200) g1.attack(r1) # print(g1.camp) # print(r1.camp) # g1.fire()
9 屬性的覆蓋(派生屬性)
子類也可以添加自己新的屬性或者在自己這裡重新定義這些屬性(不會影響到父類);
需要註意的是:一旦重新定義瞭自己的屬性且與父類重名,那麼調用新增的屬性時,就以自己為準瞭(屬性的覆蓋)。
示例:
# 派生屬性: 子類中自己添加的新屬性 # 屬性的覆蓋: 子類和父類有相同屬性,調用自己的 class Perpon: num = 20 def __init__(self, name): print('person...') class Student(Perpon): num = 10 # 把父類中的20覆蓋 def __init__(self, name, age): # age 為派生屬性 super().__init__(name) self.name = name self.age = age print('student...') def study(self): print(super().num) pass student = Student('趙四', 23) print(student.name) # person... print(student.age) print(student.num) student.study() ''' person... student... 趙四 23 10 20 '''
10 父類屬性(方法)的重用
指名道姓的重用
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) # 先找到B,B調用A,等這個線性任務處理完之後,在繼續下一行代碼 C.__init__(self) # 先找到C,C裡面也調用A的方法 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().__init__() # super(D,self).__init__() f1=D() #super()會基於mro列表,往後找 ''' D的構造方法 B的構造方法 C的構造方法 A的構造方法 ''' # super() 語法 # super(type[, object-or-type]) type 當前類,object-or-type 為實例化對象,一般默認為self,不過該參數在python3中默認
super()是一個特殊的類,調用super得到一個對象,該對象指向父類的名稱空間。
派生與繼承解決問題:子類重用父類的屬性,並派生出新的屬性。
註意:使用哪一種都可以,但不能兩種方式混合使用!
11 繼承派生機制的作用
可以將一些共用的功能加在基類中,實現代碼的共享;
在不改變基類的基礎上改變原有的功能;
練習:
list類裡隻有append向末尾加一個元素的方法,但沒有向列表頭部添加元素的方法,試想能否為列表在不改變原有功能的基礎上添加一個inster_head(x)方法,此方法能在列表的前部添加元素?
class Mylist(list): def insert_head(self,x): # self.reverse() # self.append(x) # self.reverse() self.insert(0,x) #直接在最開始插入x myl = Mylist(range(3,6)) print(myl) #[3.4.5] myl.insert_head(2) print(myl) #[2,3,4,5] myl.append(6) print(myl) #[2,3,4,5,6]
12 Super()
super(cls,obj)返回被綁定超類的實例(要求obj必須為cls類型的實例)
super() 返回被綁定超類的實例,等同於:super(class,實例方法的第一個參數,必須在方法內調用)
格式:
父類類名.方法名稱(self) 或者 super().方法名稱()或者super(本類類名,對象名)
作用:借助super()返回的實例間接調用父類的覆蓋方法;
示例:
#此示例示意用super函數間接調用父類的 class A: def work(self): print('A.work被調用') class B(A): '''B類繼承子A類''' def work(self): print('B.work被調用') def super_work(self): #調用b類自己的work方法 self.work() #調用父類的work super(B,self).work() super().work() #此種調用方式隻能在實例方法內調用 b = B() # b.work() #B.work被調用!! # super(B,b).work() #A.work被調用 b.super_work() # super_work() #出錯
調用super()會得到一個特殊的對象,該對象專門用來引用父類的屬性,且繼承順序嚴格遵循mro繼承序列;
示例:
class Father1: x =10 pass class Father2: x = 20 pass #多繼承的情況下,從左到右 class Sub(Father1,Father2): def __init__(self): #註意__int__不是__init__ print(super().__delattr__) print(Sub.mro()) # [<class '__main__.Sub'>, <class '__main__.Father1'>, <class '__main__.Father2'>, <class 'object'>] obj = Sub() print(object) #<class 'object'>
mro():會把當前類的繼承關系列出來,嚴格按照mro列表的順序往後查找
class A: #默認繼承object def test(self): print('from A.test') super().test() class B: def test(self): print('from B.test') class C(A, B): pass c = C() #檢查super的繼承順序 #mro(): 會把當前類的繼承關系列出來。 print(C.mro()) #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>] c.test() #from A.test #from B.test
使用super調用父類中的方法,註意分析程序的執行順序。
class Parent(object): def __init__(self, name, *args, **kwargs): # 為避免多繼承報錯,使用不定長參數,接受參數 print('parent的init開始被調用') self.name = name print('parent的init結束被調用') class Son1(Parent): def __init__(self, name, age, *args, **kwargs): # 為避免多繼承報錯,使用不定長參數,接受參數 print('Son1的init開始被調用') self.age = age super().__init__(name, *args, **kwargs) # 為避免多繼承報錯,使用不定長參數,接受參數 print('Son1的init結束被調用') class Son2(Parent): def __init__(self, name, gender, *args, **kwargs): # 為避免多繼承報錯,使用不定長參數,接受參數 print('Son2的init開始被調用') self.gender = gender super().__init__(name, *args, **kwargs) # 為避免多繼承報錯,使用不定長參數,接受參數 print('Son2的init結束被調用') class Grandson(Son1, Son2): def __init__(self, name, age, gender): print('Grandson的init開始被調用') # 多繼承時,相對於使用類名.__init__方法,要把每個父類全部寫一遍 # 而super隻用一句話,執行瞭全部父類的方法,這也是為何多繼承需要全部傳參的一個原因 # super(Grandson, self).__init__(name, age, gender) 效果和下面的一樣 super().__init__(name, age, gender) print('Grandson的init結束被調用') print(Grandson.__mro__) #搜索順序 gs = Grandson('grandson', 12, '男') print('姓名:', gs.name) print('年齡:', gs.age) print('性別:', gs.gender) '''結果如下: (<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>) Grandson的init開始被調用 Son1的init開始被調用 Son2的init開始被調用 parent的init開始被調用 parent的init結束被調用 Son2的init結束被調用 Son1的init結束被調用 Grandson的init結束被調用 姓名:grandson 年齡:12 性別:男 '''
註意:在上面模塊中,當在子類中通過super調用父類方法時,parent被執行瞭1次。
super調用過程:上面gs初始化時,先執行grandson中init方法, 其中的init有super調用,每執行到一次super時,都會從__mro__方法元組中順序查找搜索。
所以先調用son1的init方法,在son1中又有super調用,這個時候就就根據__mro__表去調用son2的init,然後在son2中又有super調用,這個就根據mro表又去調用parent中的init,直到調用object中的init。
所以上面的打印結果如此,要仔細分析執行過程。
重點提示:
- 1)super().__init__相對於類名.init,在單繼承上用法基本無差
- 2)但在多繼承上有區別,super方法能保證每個父類的方法隻會執行一次,而使用類名的方法會導致方法被執行多次,具體看前面的輸出結果。
- 3)多繼承時,使用super方法,對父類的傳參數,應該是由於python中super的算法導致的原因,必須把參數全部傳遞,否則會報錯。
- 4)單繼承時,使用super方法,則不能全部傳遞,隻能傳父類方法所需的參數,否則會報錯。
- 5)多繼承時,相對於使用類名.__init__方法,要把每個父類全部寫一遍, 而使用super方法,隻需寫一句話便執行瞭全部父類的方法。
這也是為何多繼承需要全部傳參的一個原因
練習一
子類調用自己的方法的時候同時調用父類的方法
class Dog(Animal): def sleep(self): # 方法一 父類.方法名(對象) # Animal.eat(self) # 方法二 super(子類,對象名).方法名() super(Dog,dog).sleep() print('去狗窩') def look_door(self): print('看門狗') dog = Dog('哈巴狗',23) dog.sleep() ''' 睡覺 去狗窩 '''
練習二
在類的外部調用super()
# 類外部調用super() dog = Dog('哈巴狗', 23) super(Dog, dog).sleep() # super(子類,對象名).方法名() dog.sleep() ''' 睡覺 去狗窩 '''
到此這篇關於python語法學習之super(),繼承與派生的文章就介紹到這瞭,更多相關python 繼承與派生內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 終於搞懂瞭Python中super(XXXX, self).__init__()的作用瞭
- Python基礎之面向對象進階詳解
- python 面向對象三大特征詳解
- Python編程super應用場景及示例解析
- Python中super()的理解以及應用場景實例