淺談Python的方法解析順序(MRO)
方法解析順序, Method Resolution Order
從一段代碼開(kāi)始
考慮下面的情況:
class A(object): def foo(self): print('A.foo()') class B(object): def foo(self): print('B.foo()') class C(B, A): pass c = C() c.foo()
C同時(shí)繼承了類A和類B, 它們都有各自的foo()方法. 那么C的實(shí)例c調(diào)用foo()方法時(shí), 到底是調(diào)用A.foo()還是B.foo()?
__mro__
Python的每一個(gè)有父類的類都有一個(gè)與方法解析順序相關(guān)的特殊屬性:__mro__, 它是一個(gè)tuple, 裝著方法解析時(shí)的對(duì)象查找順序: 越靠前的優(yōu)先級(jí)越高. 執(zhí)行下面的代碼:
print type(C.__mro__)
print C.__mro__
輸出:
<type 'tuple'>
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>)
可以看到, B在C的前面, 所以在上一段代碼中, c.foo()調(diào)用的是B.foo()而不是A.foo().
之所以B在C的前面, 是因?yàn)樵谥付–的父類時(shí)先指定了B:
class C(B, A):
若將它改成:
class C(A, B):
c.foo()執(zhí)行的就是A.foo()了.
熟悉環(huán)境變量的可以將__mro__理解為以目標(biāo)對(duì)象為環(huán)境的PATH變量: 從左到右開(kāi)始查找, 找到就執(zhí)行, 然后返回結(jié)果.
方法解析順序
從C.__mro__的值可以看出, Python的方法解析優(yōu)先級(jí)從高到低為:
1. 實(shí)例本身(instance)
2. 類(class)
3. super class, 繼承關(guān)系越近, 越先定義, 優(yōu)先級(jí)越高.
其實(shí)屬性解析順序也基本一致, 只不過(guò)多了個(gè)__getattr__的查找(見(jiàn)Python對(duì)象的屬性訪問(wèn)過(guò)程).
補(bǔ)充知識(shí):python中的單繼承,多繼承和mro順序
python作為一門(mén)動(dòng)態(tài)語(yǔ)言,是和c++一樣支持面向?qū)ο缶幊痰?。相?duì)對(duì)象編程有三大特性,分別是繼承,封裝和多態(tài)。今天我們重點(diǎn)講解的是,python語(yǔ)言中的單繼承和多繼承。
繼承概念:
如果一個(gè)類繼承了另外一個(gè)類時(shí),它將自動(dòng)獲得另一個(gè)類的所有屬性和方法,那么原有的類稱為父類,而新類稱為子類。子類繼承了其父類的所有屬性和方法。同時(shí)還可以定義自己的屬性和方法。
單繼承就是一個(gè)子類只能繼承一個(gè)父類。
格式: class 子類(父類)
舉例: class A(B)
A類擁有了B類的所有的特征,A類繼承了B類
B類 父類,基類
A類 子類 派生類 后代類
繼承的作用:功能的升級(jí)和擴(kuò)展
功能的升級(jí)就是對(duì)原有 的功能進(jìn)行完善重新,功能的擴(kuò)展就是對(duì)原本沒(méi)有的功能進(jìn)行添加。減少代碼的冗余。
下面我們舉一個(gè)單繼承的例子:
class Dog(): #父類 def __init__(self): #父類的屬性初始化 self.name='狗' self.leg=4 def __str__(self): return "名字:%s %d 條腿"%(self.name,self.leg) class Taidi(Dog): #定義一個(gè)Taidi 泰迪 類繼承自Dog類 -->單繼承 pass taidi=Taidi() print(taidi) 輸出結(jié)果--> 名字:狗 4 條腿
多繼承:
多繼承就是一個(gè)子類同時(shí)繼承自多個(gè)父類,又稱菱形繼承、鉆石繼承。
首先,我們先講多繼承中一個(gè)常見(jiàn)方法,單獨(dú)調(diào)用父類的方法。在子類初始化的時(shí)候需要手動(dòng)調(diào)用父類的初始化方法進(jìn)行父類的屬性的構(gòu)造,不然就不能使用提供的屬性。
在子類中調(diào)用父類的初始化方法格式就是: 父類名._init_(self)
下面舉一個(gè)單獨(dú)調(diào)用父類方法的例子:
print("******多繼承使用類名.__init__ 發(fā)生的狀態(tài)******") class Parent(object): #父類 def __init__(self, name): print('parent的init開(kāi)始被調(diào)用') self.name = name #屬性的初始化 print('parent的init結(jié)束被調(diào)用') class Son1(Parent): #單繼承 Son1子類繼承父類 def __init__(self, name, age): print('Son1的init開(kāi)始被調(diào)用') self.age = age Parent.__init__(self, name) #單獨(dú)調(diào)用父類的屬性 print('Son1的init結(jié)束被調(diào)用') class Son2(Parent): #也是單繼承 Son2繼承父類 def __init__(self, name, gender): print('Son2的init開(kāi)始被調(diào)用') self.gender = gender #單獨(dú)調(diào)用父類的初始化屬性方法 Parent.__init__(self, name) print('Son2的init結(jié)束被調(diào)用') class Grandson(Son1, Son2): #多繼承,繼承兩個(gè)父類 def __init__(self, name, age, gender): print('Grandson的init開(kāi)始被調(diào)用') Son1.__init__(self, name, age) # 單獨(dú)調(diào)用父類的初始化方法 Son2.__init__(self, name, gender) print('Grandson的init結(jié)束被調(diào)用') gs = Grandson('grandson', 18, '男') #實(shí)例化對(duì)象 print('姓名:', gs.name) print('年齡:', gs.age) print('性別:', gs.gender) print("******多繼承使用類名.__init__ 發(fā)生的狀態(tài)******\n\n")
下面讓我們看看運(yùn)行的結(jié)果:
******多繼承使用類名.__init__ 發(fā)生的狀態(tài)****** Grandson的init開(kāi)始被調(diào)用 Son1的init開(kāi)始被調(diào)用 parent的init開(kāi)始被調(diào)用 parent的init結(jié)束被調(diào)用 Son1的init結(jié)束被調(diào)用 Son2的init開(kāi)始被調(diào)用 parent的init開(kāi)始被調(diào)用 parent的init結(jié)束被調(diào)用 Son2的init結(jié)束被調(diào)用 Grandson的init結(jié)束被調(diào)用 姓名: grandson 年齡: 18 性別: 男 ******多繼承使用類名.__init__ 發(fā)生的狀態(tài)******
mro順序
查看上面的運(yùn)行結(jié)果,我們發(fā)現(xiàn)由于多繼承情況,parent類被的屬性被構(gòu)造了兩次,如果在更加復(fù)雜的結(jié)構(gòu)下可能更加嚴(yán)重。
為了解決這個(gè)問(wèn)題,Python官方采用了一個(gè)算法將復(fù)雜結(jié)構(gòu)上所有的類全部都映射到一個(gè)線性順序上,而根據(jù)這個(gè)順序就能夠保證所有的類都會(huì)被構(gòu)造一次。這個(gè)順序就是MRO順序。
格式:
類名._mro_()
類名.mro()
多繼承中super調(diào)用有所父類的被重寫(xiě)的方法
super本質(zhì)上就是使用MRO這個(gè)順序去調(diào)用 當(dāng)前類在MRO順序中下一個(gè)類。 super().init()則調(diào)用了下一個(gè)類的初始化方法進(jìn)行構(gòu)造。
print("******多繼承使用super().__init__ 發(fā)生的狀態(tài)******") class Parent(object): def __init__(self, name, *args, **kwargs): # 為避免多繼承報(bào)錯(cuò),使用不定長(zhǎng)參數(shù),接受參數(shù) print('parent的init開(kāi)始被調(diào)用') self.name = name print('parent的init結(jié)束被調(diào)用') class Son1(Parent): def __init__(self, name, age, *args, **kwargs): # 為避免多繼承報(bào)錯(cuò),使用不定長(zhǎng)參數(shù),接受參數(shù) print('Son1的init開(kāi)始被調(diào)用') self.age = age super().__init__(name, *args, **kwargs) # 為避免多繼承報(bào)錯(cuò),使用不定長(zhǎng)參數(shù),接受參數(shù) print('Son1的init結(jié)束被調(diào)用') class Son2(Parent): def __init__(self, name, gender, *args, **kwargs): # 為避免多繼承報(bào)錯(cuò),使用不定長(zhǎng)參數(shù),接受參數(shù) print('Son2的init開(kāi)始被調(diào)用') self.gender = gender super().__init__(name, *args, **kwargs) # 為避免多繼承報(bào)錯(cuò),使用不定長(zhǎng)參數(shù),接受參數(shù) print('Son2的init結(jié)束被調(diào)用') class Grandson(Son1, Son2): def __init__(self, name, age, gender): print('Grandson的init開(kāi)始被調(diào)用') # 多繼承時(shí),相對(duì)于使用類名.__init__方法,要把每個(gè)父類全部寫(xiě)一遍 # 而super只用一句話,執(zhí)行了全部父類的方法,這也是為何多繼承需要全部傳參的一個(gè)原因 # super(Grandson, self).__init__(name, age, gender) super().__init__(name, age, gender) print('Grandson的init結(jié)束被調(diào)用') print(Grandson.__mro__) gs = Grandson('grandson', 18, '男') print('姓名:', gs.name) print('年齡:', gs.age) print('性別:', gs.gender) print("******多繼承使用super().__init__ 發(fā)生的狀態(tài)******\n\n")
查看下運(yùn)行結(jié)果:
******多繼承使用super().__init__ 發(fā)生的狀態(tài)****** (<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>) Grandson的init開(kāi)始被調(diào)用 Son1的init開(kāi)始被調(diào)用 Son2的init開(kāi)始被調(diào)用 parent的init開(kāi)始被調(diào)用 parent的init結(jié)束被調(diào)用 Son2的init結(jié)束被調(diào)用 Son1的init結(jié)束被調(diào)用 Grandson的init結(jié)束被調(diào)用 姓名: grandson 年齡: 18 性別: 男 ******多繼承使用super().__init__ 發(fā)生的狀態(tài)******
單繼承中super
print("******單繼承使用super().__init__ 發(fā)生的狀態(tài)******") class Parent(object): def __init__(self, name): print('parent的init開(kāi)始被調(diào)用') self.name = name print('parent的init結(jié)束被調(diào)用') class Son1(Parent): def __init__(self, name, age): print('Son1的init開(kāi)始被調(diào)用') self.age = age super().__init__(name) # 單繼承不能提供全部參數(shù) print('Son1的init結(jié)束被調(diào)用') class Grandson(Son1): def __init__(self, name, age, gender): print('Grandson的init開(kāi)始被調(diào)用') super().__init__(name, age) # 單繼承不能提供全部參數(shù) print('Grandson的init結(jié)束被調(diào)用') gs = Grandson('grandson', 12, '男') print('姓名:', gs.name) print('年齡:', gs.age) #print('性別:', gs.gender) print("******單繼承使用super().__init__ 發(fā)生的狀態(tài)******\n\n")
運(yùn)行結(jié)果:
******單繼承使用super().__init__ 發(fā)生的狀態(tài)****** Grandson的init開(kāi)始被調(diào)用 Son1的init開(kāi)始被調(diào)用 parent的init開(kāi)始被調(diào)用 parent的init結(jié)束被調(diào)用 Son1的init結(jié)束被調(diào)用 Grandson的init結(jié)束被調(diào)用 姓名: grandson 年齡: 12 ******單繼承使用super().__init__ 發(fā)生的狀態(tài)******
下面讓我們總結(jié)下:
MRO保證了多繼承情況 每個(gè)類只出現(xiàn)一次
super().__init__相對(duì)于類名.init,在單繼承上用法基本無(wú)差
但在多繼承上有區(qū)別,super方法能保證每個(gè)父類的方法只會(huì)執(zhí)行一次,而使用類名的方法會(huì)導(dǎo)致方法被執(zhí)行多次
多繼承時(shí),使用super方法,對(duì)父類的傳參數(shù),應(yīng)該是由于python中super的算法導(dǎo)致的原因,必須把參數(shù)全部傳遞,否則會(huì)報(bào)錯(cuò)
單繼承時(shí),使用super方法,則不能全部傳遞,只能傳父類方法所需的參數(shù),否則會(huì)報(bào)錯(cuò)
多繼承時(shí),相對(duì)于使用類名.__init__方法,要把每個(gè)父類全部寫(xiě)一遍,而使用super方法,只需寫(xiě)一句話便執(zhí)行了全部父類的方法,這也是為何多繼承需要全部傳參的一個(gè)原因
下面是一個(gè)簡(jiǎn)答的面試題:
class Parent(object): x = 1 class Child1(Parent): pass class Child2(Parent): pass print(Parent.x, Child1.x, Child2.x) Child1.x = 2 print(Parent.x, Child1.x, Child2.x) Parent.x = 3 print(Parent.x, Child1.x, Child2.x)
運(yùn)行結(jié)果:
1 1 1 1 2 1 3 2 3
以上這篇淺談Python的方法解析順序(MRO)就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
python實(shí)現(xiàn)FTP文件傳輸?shù)姆椒ǎǚ?wù)器端和客戶端)
FTP(File Transfer Protocol,文件傳輸協(xié)議) 是 TCP/IP 協(xié)議組中的協(xié)議之一。接下來(lái)通過(guò)本文給大家介紹關(guān)于python實(shí)現(xiàn)FTP文件傳輸?shù)南嚓P(guān)知識(shí)(服務(wù)器端和客戶端) ,需要的朋友可以參考下2020-03-03Python地理地圖可視化folium標(biāo)記點(diǎn)彈窗設(shè)置代碼(推薦)
這篇文章主要介紹了Python地理地圖可視化folium標(biāo)記點(diǎn)彈窗設(shè)置,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09在Python的Django框架中獲取單個(gè)對(duì)象數(shù)據(jù)的簡(jiǎn)單方法
這篇文章主要介紹了在Python的Django框架中獲取單個(gè)對(duì)象數(shù)據(jù)的簡(jiǎn)單方法,Django為數(shù)據(jù)的操作提供了諸多方便的功能,需要的朋友可以參考下2015-07-07Django與數(shù)據(jù)庫(kù)交互的實(shí)現(xiàn)
最近在學(xué)習(xí)Django,本文主要介紹了Django與數(shù)據(jù)庫(kù)交互的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06Python實(shí)現(xiàn)查找數(shù)據(jù)庫(kù)最接近的數(shù)據(jù)
這篇文章主要介紹了Python實(shí)現(xiàn)查找數(shù)據(jù)庫(kù)最接近的數(shù)據(jù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06如何利用python實(shí)現(xiàn)windows的批處理及文件夾操作
最近工作中需要幾個(gè)腳本運(yùn)行其他程序,幾乎像一個(gè)Windows批處理文件,這篇文章主要給大家介紹了關(guān)于如何利用python實(shí)現(xiàn)windows的批處理及文件夾操作的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-01-01python腳本設(shè)置超時(shí)機(jī)制系統(tǒng)時(shí)間的方法
這篇文章主要介紹了python腳本設(shè)置超時(shí)機(jī)制系統(tǒng)時(shí)間的方法,感興趣的小伙伴們可以參考一下2016-02-02