Python學(xué)習(xí)之MRO方法搜索順序
為什么會(huì)講 MRO?
- 在講多繼承的時(shí)候,有講到, 當(dāng)繼承的多個(gè)父類(lèi)擁有同名屬性、方法,子類(lèi)對(duì)象調(diào)用該屬性、方法時(shí)會(huì)調(diào)用哪個(gè)父類(lèi)的屬性、方法呢?
- 這就取決于 Python 的 MRO 了
什么是 MRO
- MRO,method resolution order,方法搜索順序
- 對(duì)于單繼承來(lái)說(shuō),MRO 很簡(jiǎn)單,從當(dāng)前類(lèi)開(kāi)始,逐個(gè)搜索它的父類(lèi)有沒(méi)有對(duì)應(yīng)的屬性、方法
- 所以 MRO 更多用在多繼承時(shí)判斷方法、屬性的調(diào)用路徑
- Python 中針對(duì)類(lèi)提供了一個(gè)內(nèi)置屬性
__mro__可以查看方法搜索順序
實(shí)際代碼
class A:
def test(self):
print("AAA-test")
class B:
def test(self):
print("BBB-test")
# 繼承了三個(gè)類(lèi),B、A、還有默認(rèn)繼承的 object
class C(B, A):
...
# 通過(guò)類(lèi)對(duì)象調(diào)用,不是實(shí)例對(duì)象!
print(C.__mro__)
# 輸出結(jié)果
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
- 1.在搜索方法時(shí),是按照
__mro__的輸出結(jié)果從左往右的順序查找的 - 2.如果在當(dāng)前類(lèi)(Class C)中找到方法,就直接執(zhí)行,不再搜索
- 3.如果沒(méi)有找到,就查找下一個(gè)類(lèi)中(Class B)是否有對(duì)應(yīng)的方法,如果找到,就直接執(zhí)行,不再搜素
- 4.如果找到最后一個(gè)類(lèi)(Class object)都沒(méi)有找到方法,程序報(bào)錯(cuò)
類(lèi)圖

注意
其實(shí) MRO 是涉及一個(gè)底層算法的,下面來(lái)詳細(xì)講解一下
MRO 算法
Python 發(fā)展到現(xiàn)在經(jīng)歷了三種算法
- 舊式類(lèi) MRO 算法:從左往右,采用深度優(yōu)先搜索(DFS),從左往右的算法,稱(chēng)為舊式類(lèi)的 MRO
- 新式類(lèi) MRO 算法:自 Python 2.2 版本開(kāi)始,新式類(lèi)在采用深度優(yōu)先搜索算法的基礎(chǔ)上,對(duì)其做了優(yōu)化
- C3 算法:自 Python 2.3 版本,對(duì)新式類(lèi)采用了 C3 算法;由于 Python 3.x 僅支持新式類(lèi),所以該版本只使用 C3 算法
什么是舊式類(lèi),新式類(lèi)
Python學(xué)習(xí)之新式類(lèi)和舊式類(lèi)講解
想深入了解 C3 算法的可以看看官網(wǎng)
https://www.python.org/download/releases/2.3/mro/
舊式類(lèi) MRO 算法
需要在 python2 環(huán)境下運(yùn)行這段代碼
實(shí)際代碼
# 舊式類(lèi)算法
class A:
def test(self):
print("CommonA")
class B(A):
pass
class C(A):
def test(self):
print("CommonC")
class D(B, C):
pass
D().test()
# python2 下的運(yùn)行結(jié)果
CommonA

類(lèi)圖

分析
- 通過(guò)類(lèi)圖可以看到,此程序中的 4 個(gè)類(lèi)是一個(gè)“菱形”繼承的關(guān)系
- 當(dāng)使用 D 類(lèi)實(shí)例對(duì)象訪(fǎng)問(wèn) test() 方法時(shí),根據(jù)深度優(yōu)先算法,搜索順序?yàn)?code>D->B->A->C->A
- 因此,舊式類(lèi) MRO 算法最先搜索得到 test() 方法是在 A 類(lèi)里面,所以最終輸出結(jié)果為 CommonA
新式類(lèi) MRO 算法
- 為解決舊式類(lèi) MRO 算法存在的問(wèn)題,Python 2.2 版本推出了新的計(jì)算新式類(lèi) MRO 的方法
- 它仍然采用從左至右的深度優(yōu)先遍歷,但是如果遍歷中出現(xiàn)重復(fù)的類(lèi),只保留最后一個(gè)
以上面的代碼栗子來(lái)講
- 深度優(yōu)先遍歷,搜索順序?yàn)?code>D->B->A->C->A
- 因?yàn)轫樞蛑杏?2 個(gè) A,因此只保留最后一個(gè)
- 最終搜索順序?yàn)?code>D->B->C->A
新式 MRO 算法的問(wèn)題
雖然解決了舊式 MRO 算法的問(wèn)題,但可能會(huì)違反單調(diào)性原則
什么是單調(diào)性原則?
在子類(lèi)存在多繼承時(shí),子類(lèi)不能改變父類(lèi)的 MRO 搜索順序,否則會(huì)導(dǎo)致程序發(fā)生異常
實(shí)際代碼
class X(object):
pass
class Y(object):
pass
class A(X, Y):
pass
class B(Y, X):
pass
class C(A, B):
pass
深度優(yōu)先遍歷后的搜索順序?yàn)椋?code>C->A->X->object->Y->object->B->Y->object->X->object
相同取后者的搜索順序?yàn)椋?code>C->A->B->Y->X->object
分析不同類(lèi)的 MRO
- A:
A->X->Y->object - B:
A->Y->X->object - C:
C->A->B->X->Y->object
很明顯,B、C 中間的 X、Y 順序是相反的,就是說(shuō) B 被繼承時(shí),它的搜索順序會(huì)被改變,違反了單調(diào)性
在 python2 中運(yùn)行這段代碼的報(bào)錯(cuò)

在 python3 中運(yùn)行這段代碼的報(bào)錯(cuò)

C3 MRO 算法
- 為解決前面兩個(gè)算法的問(wèn)題,Python 2.3 采用了 C3 方法來(lái)確定方法搜索順序
- 多數(shù)情況下,如果別人提到 Python 中的 MRO,指的都是 C3 算法
將上面第一個(gè)栗子的代碼放到 python3 中運(yùn)行
class A:
def test(self):
print("CommonA")
class B(A):
pass
class C(A):
def test(self):
print("CommonC")
class D(B, C):
pass
D().test()
# 輸出結(jié)果
CommonC
簡(jiǎn)單了解下 C3 算法
以上面代碼為栗子,C3 會(huì)把各個(gè)類(lèi)的 MRO 等價(jià)為以下等式
- A:L[A] = merge(A , object)
- B:L[B] = B + merge(L[A] , A)
- C:L[C] = C + merge(L[A] , A)
- D:L[D] = D + merge(L[B] , L[C] , B , C)
了解一下:頭、尾
以 A 類(lèi)為栗,merge() 包含的 A 成為 L[A] 的頭,剩余元素(這里只有 object)稱(chēng)為尾
merge 的運(yùn)算方式
- 1.將merge 第一個(gè)列表的頭元素(如 L[A] 的頭),記作 H
- 2.如果 H 出現(xiàn)在 merge 其他列表的頭部,則將其輸出,并將其從所有列表中刪除
- 3.如果 H 只出現(xiàn)一次,那么也將其輸出,并將其從所有列表中刪除
- 4.如果 H 出現(xiàn)在 merge 其他列表的非頭部,則取下一個(gè)列表的頭元素記作 H,然后回到步驟二
- 5.最后回到步驟一,重復(fù)以上步驟
重復(fù)以上步驟直到列表為空,則算法結(jié)束;如果不能再找出可以輸出的元素,則拋出異常
簡(jiǎn)單類(lèi) MRO 的計(jì)算栗子
class B(object): pass print(B.__mro__) (<class '__main__.B'>, <class 'object'>)
- MRO 計(jì)算方式
L[B] = L[B(object)]
= B + merge(L[object])
= B + L[object]
= B object
單繼承MRO 的計(jì)算栗子
# 計(jì)算 MRO class B(object): pass class C(B): pass print(C.__mro__) (<class '__main__.C'>, <class '__main__.B'>, <class 'object'>)
- MRO 計(jì)算方式
L[C] = C + merge(L[B])
= C + L[B]
= C B object
多繼承MRO 的計(jì)算栗子
O = object class F(O): pass class E(O): pass class D(O): pass class C(D, F): pass class B(D, E): pass class A(B, C): pass print(C.__mro__) print(B.__mro__) print(A.__mro__)
# 輸出結(jié)果
(<class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>)
(<class '__main__.B'>, <class '__main__.D'>, <class '__main__.E'>, <class 'object'>)
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class 'object'>)
- O 類(lèi)、object 類(lèi) MRO 計(jì)算
L[O] = O = object
- D、E、F 類(lèi) MRO 計(jì)算
L[D] = D + merge(L[O])
= D O
- C 類(lèi) MRO 計(jì)算
L[C] = L[C(D, F)]
= C + merge(L[D], L[F], DF)
# 從前面可知 L[D] 和 L[F] 的結(jié)果
= C + merge(DO, FO, DF)
# 因?yàn)?D 是順序第一個(gè)并且在幾個(gè)包含 D 的 list 中是 head,
# 所以這一次取 D 同時(shí)從列表中刪除 D
= C + D + merge(O, FO, F)
# 因?yàn)?O 雖然是順序第一個(gè)但在其他 list (FO)中是在尾部, 跳過(guò)
# 改為檢查第二個(gè)list FO
# F 是第二個(gè) list 和其他 list 的 head
# 取 F 同時(shí)從列表中刪除 F
= C + D + F + merge(O)
= C D F O
- B 類(lèi) MRO 計(jì)算
L[B] = L[B(D, E)]
= B + merge(L[D], L[E], DE)
= B + merge(DO, EO, DE)
= B + D + merge(O, EO, E)
= B + D + E + merge(O)
= B D E O
- A 類(lèi) MRO 計(jì)算
L[A] = L[A(B,C)]
= A + merge(L[B], L[C], BC)
= A + merge( BDEO, CDFO, BC )
= A + B + merge( DEO, CDFO, C )
# D 在其他列表 CDFO 不是 head,所以跳過(guò)到下一個(gè)列表的 頭元素 C
= A + B + C + merge( DEO, DFO )
= A + B + C + D + merge( EO, FO )
= A + B + C + D + E + merge( O, FO )
= A + B + C + D + E + F + merge( O )
= A B C D E F O
多繼承MRO 的計(jì)算栗子二
O = object class F(O): pass class E(O): pass class D(O): pass class C(D, F): pass class B(E, D): pass class A(B, C): pass print(C.__mro__) print(B.__mro__) print(A.__mro__)
# 輸出結(jié)果
(<class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>)
(<class '__main__.B'>, <class '__main__.E'>, <class '__main__.D'>, <class 'object'>)
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>)
- O 類(lèi)、object 類(lèi) MRO 計(jì)算
L[O] = O = object
- D、E、F 類(lèi) MRO 計(jì)算
L[D] = D + merge(L[O])
= D O
- C 類(lèi) MRO 計(jì)算
L[C] = L[C(D, F)]
= C + merge(L[D], L[F], DF)
= C + merge(DO, FO, DF)
= C + D + merge(O, FO, F)
= C + D + F + merge(O)
= C D F O
- B 類(lèi) MRO 計(jì)算
L[B] = L[B(E, D)]
= B + merge(L[E], L[D], ED)
= B + merge(EO, DO, ED)
= B + E + merge(O, DO, D)
= B + E + D + merge(O)
= B E D O
- A 類(lèi) MRO 計(jì)算
L[A] = L[A(B, C)]
= A + merge(L[B], L[C], BC)
= A + merge(BEDO, CDFO, BC)
= A + B + merge(EDO, CDFO, C)
= A + B + E + merge(DO,CDFO, C)
= A + B + E + C + merge(O,DFO)
= A + B + E + C + D + merge(O, FO)
= A + B + E + C + D + F + merge(O)
= A B E C D F O
到此這篇關(guān)于Python學(xué)習(xí)之MRO方法搜索順序的文章就介紹到這了,更多相關(guān)Python MRO方法搜索順序內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python批量自動(dòng)修改文件名,按指定的格式自動(dòng)命名方式
這篇文章主要介紹了Python批量自動(dòng)修改文件名,按指定的格式自動(dòng)命名方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08
如何用Python和JS實(shí)現(xiàn)的Web SSH工具
這篇文章主要介紹了如何用Python和JS實(shí)現(xiàn)的Web SSH工具,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
python實(shí)現(xiàn)數(shù)據(jù)分析與建模
這篇文章主要介紹了python實(shí)現(xiàn)數(shù)據(jù)分析與建模功能,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07
Python OOP類(lèi)中的幾種函數(shù)或方法總結(jié)
今天小編就為大家分享一篇關(guān)于Python OOP類(lèi)中的幾種函數(shù)或方法總結(jié),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02
python小練習(xí)之爬魷魚(yú)游戲的評(píng)價(jià)生成詞云
讀萬(wàn)卷書(shū)不如行萬(wàn)里路,只學(xué)書(shū)上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Python爬取熱火的魷魚(yú)游戲評(píng)價(jià),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平2021-10-10
python自動(dòng)化發(fā)送郵件實(shí)例講解
在本篇文章里小編給大家分享了一篇關(guān)于python自動(dòng)化發(fā)送郵件實(shí)例講解內(nèi)容,有興趣的朋友們可以學(xué)習(xí)參考下。2021-01-01
利用Python實(shí)現(xiàn)Shp格式向GeoJSON的轉(zhuǎn)換方法
JSON(JavaScript Object Nonation)是利用鍵值對(duì)+嵌套來(lái)表示數(shù)據(jù)的一種格式,以其輕量、易解析的優(yōu)點(diǎn),這篇文章主要介紹了利用Python實(shí)現(xiàn)Shp格式向GeoJSON的轉(zhuǎn)換,需要的朋友可以參考下2019-07-07
win11環(huán)境下python如何通過(guò)命令行升級(jí)版本詳解
在Windows上升級(jí)Python有多種方法,下面這篇文章主要給大家介紹了關(guān)于win11環(huán)境下python如何通過(guò)命令行升級(jí)版本的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07

