本文目錄一覽:
- 1、Python入門精華-OOP調用父類的方法及MRO方法解析序列
- 2、【面向對象】Python面向對象之多繼承算法
- 3、python的類和對象中的super函數的問題
- 4、學明白了 Python 多繼承中的 MRO 算法有什麼用
Python入門精華-OOP調用父類的方法及MRO方法解析序列
在繼承關係中,我們想調用已經被覆蓋了的父類的方法,就需要如下實現:
解決方法:
要調用父類中的方法,就要使用超類(超集)方法super(),該方法旨在調用已經被覆蓋的父類的成員方法。
討論:
有關python是如何實現繼承的?
針對每一個定義的類,都會計算出一個成為方法解析順序(MRO)的元組,其只是簡單的對所有基類進行簡單地線性排列。
通過上述的C類調用MRO表,我們不難看出,它將本類開始一直到object類直接所有的父類一次性從左向右逐層向上的排列了出來(先排列自己,在排列自己的父類,最後排列父類的父類,以及最後的object)
然而MRO為何如此排列,這裡要涉及到一個非常令人討厭的數學算法,C3線性化處理,這裡只是總結其三個約束:(簡單點說,其實就是對父類進行歸併排列)
1、先檢查子類,再檢查父類
2、有多個父類時,按照MRO表的順序依次查看
3、如果下一個待選的類出現了兩個合法的選擇,那麼就從第一個父類中選取。
4、補充一點:MRO對類的排序幾乎適用於任何定義的類層次結構。
來了來了,它真的來了:重點~~
有很多同學是否仔細看過上邊的代碼?
有關super()函數,以下重點需要各位明白:
在重寫的方法中僅使用一次super()方法時,會按照MRO表從下一個類開始搜索對應的方法或屬性,以此類推。 所以C中重寫了父類的構造,構造中有super,所以會按照順序去查找MRO中下一個類的方法,發現A中也有super,就會再去B中找對應的方法(同名方法是__init__),所以找到B的構造,可是B中又有super,就會再去MRO中B的下一個類(Base)中找對應的方法(Base的__init__()方法),所以會先打印“Base.__init__”,打印完後又因為B的__init__中還有打印“B.__init__”,所以接着打印‘B.__init__’,又因為打印完後A中還有打印“A.__init__”,所以再打印“A.__init__”,最後打印“C.__init__”。這樣就可以遍歷MRO整張表中所有的對應的__init__()方法,並且讓每個方法只會被調用一次。
為了更好的記憶:當所有重寫的方法中只使用了一次super函數時,會從最上層的類依次調用其指定的方法即可以理解為(object-Base-B-A-C)。
所以,輸出結果為:
甚至於如下情況更為耐人尋味,仔細品一品:
值的一提的是:AB均沒有顯式的繼承的父類,為何結果為打印‘AB’呢?這裡就要理解MRO的含義了哦!
【面向對象】Python面向對象之多繼承算法
Python的類分為經典類和新式類:
官方推薦使用新式類替換經典類,因為經典類對於多重繼承採用的從左到右深度優先匹配算法存在一些問題。也就是如果方法同名,有的時候會繞過一些想要訪問的方法,只指向一個方法。
2.x版本中使用的是深度優先算法,而3.x版本使用的是c3算法,和廣度優先算法在某些情況下是不一樣的
以頂點為起始點,從左到右開始遍歷,當遍歷到一個節點的時候,判斷是否有左右兩個頂點,優先選擇左邊的作為頂點,再繼續遍歷下去,當遍歷完成後,選擇未曾訪問的頂點重新遍歷
如圖:根據深度優先算法,就是v1-v2-v4-v5-v3-v6
以頂點為起始點,從左到右開始遍歷,一層一層地向下遍歷,從左到右
如圖:根據廣度優先算法:就是v1-v2-v3-v4-v6-v5
C3 算法:MRO是一個有序列表L,在類被創建時就計算出來。
L(Child(Base1,Base2)) = [ Child + merge( L(Base1) , L(Base2) , Base1Base2 )]
L(object) = [ object ]
L的性質:結果為列表,列表中至少有一個元素即類自己。
“+” : 添加到列表的末尾,即 [ A + B ] = [ A,B ]
merge: ① 如果列表空則結束,非空 讀merge中第一個列表的表頭,
② 查看該表頭是否在 merge中所有列表的表尾中,或者不在表尾的第一個字母
②–③ 不在,則 放入 最終的L中,並從merge中的所有列表中刪除,然後 回到①中
②–④ 在,查看 當前列表是否是merge中的最後一個列表
④–⑤ 不是 ,跳過當前列表,讀merge中下一個列表的表頭,然後 回到 ②中
④–⑥ 是,異常。類定義失敗。
表頭: 列表的第一個元素 (列表:ABC,那麼表頭就是A,B和C就是表尾)
表尾: 列表中表頭以外的元素集合(可以為空)
merge 簡單的說即尋找合法表頭(也就是不在表尾中的表頭),如果所有表中都未找到合法表頭則異常。
例如:
L(D) = L(D(O))
= D + merge(L(O))
= D + O
= [D,O]
L(B) = L(B(D,E))
= B + merge(L(D) , L(E))
= B + merge(DO , EO) # 第一個列表DO的表頭D,其他列表比如EO的表尾都不含有D,所以可以將D提出來,即D是合法表頭
= B + D + merge(O , EO) #從第一個開始表頭是O,但是後面的列表EO的表尾中含有O所以O是不合法的,所以跳到下一個列表EO
= B + D + E + merge(O , O)
= [B,D,E,O]
同理:
L(C) = [C,E,F,O]
L(A(B,C)) = A + merge(L(B),L(C),BC)
= A + merge(BDEO,CEFO,BC)#B是合法表頭
= A + B + merge(DEO,CEFO,C)#D是合法表頭
= A + B + D + merge(EO,CEFO,C)#E不是合法表頭,跳到下一個列表CEFO,此時C是合法表頭
= A + B + D + C + merge(EO,EFO)#由於第三個列表中的C被刪除,為空,所以不存在第三個表,只剩下兩個表;此時E是合法表頭
= A + B + D + C + E + merge(O,FO)#O不是合法表頭,跳到下一個列表FO,F是合法表頭,
= A + B + D + C + E + F + merge(O,O)#O是合法表頭
= A + B + D + C + E + F + O
= [A,B,D,C,E,F,O]
L(D)
= L(D(B,C)) 取出D
= D + merge(BA+CA+BC) 查看merge的表頭,取出B,去除C,剩下A
= D + B + C + A
python的類和對象中的super函數的問題
問題一
因為在B類中調用了super方法,所以沒有執行完B類就去執行C類的程序
super方法在多重繼承程序中的調用順序,採用的是C3算法(在python3中)。
C3算法的規則如下
①.從底層開始,選擇入邊為零的點。
②.從左到右。
③深度探索。但受限於②規則。
每一個類都可以用mro函數查看自己的繼承順序(MRO全稱Method Resolution Order,就是用來定義繼承方法的調用順序)
對於你的程序
分析
①規則。得到D類,去掉D類以後,入邊為零的是B類和C類
②規則。選擇B類,去掉B類後,入邊為零的只有C類。結論是D–B–C–A。
在d=D()語句前加print(D.mro()),就可以打印出D類的繼承順序
[class ‘__main__.D’, class ‘__main__.B’, class ‘__main__.C’, class ‘__main__.A’, class ‘object’]
問題二
python3的繼承不同於普通的繼承,super函數已經考慮到了重複繼承的問題,所以對於A類只訪問一次
學明白了 Python 多繼承中的 MRO 算法有什麼用
可能你有C++的基礎吧,C++確實可以不用帶上this。
我把你的代碼稍微改了一下,你可以分別看看cls1.n、cls2.n、self.n的輸出。
class cls1:
n=3
class cls2(cls1):
def __init__(self,cls1=cls1):
cls2.n += 1
self.n += 2
print ‘cls1.n:%s’%cls1.n
print ‘cls2.n:%s’%cls2.n
print ‘self.n:%s’%self.n
cls2()
print ‘cls1.n:%s’%cls1.n
可以看出兩個類和一個對象都可以在這裡調用它們的n屬性,如果只敲一個n別人讀你的代碼時就比較容易產生岐義,雖然少打了5個字符,但是不符合簡單明確的pythonic哲學。
當然你也可以就認為python語法規則就是這麼規定的。
原創文章,作者:FLGKA,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/128786.html