1. 程式人生 > 程式設計 >淺談Python的方法解析順序(MRO)

淺談Python的方法解析順序(MRO)

方法解析順序,Method Resolution Order

從一段程式碼開始

考慮下面的情況:

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同時繼承了類A和類B,它們都有各自的foo()方法. 那麼C的例項c呼叫foo()方法時,到底是呼叫A.foo()還是B.foo()?

__mro__

Python的每一個有父類的類都有一個與方法解析順序相關的特殊屬性:__mro__,它是一個tuple,裝著方法解析時的物件查詢順序: 越靠前的優先順序越高. 執行下面的程式碼:

print type(C.__mro__)
print C.__mro__

輸出:

<type 'tuple'>
(<class '__main__.C'>,<class '__main__.B'>,<class '__main__.A'>,<type 'object'>)

可以看到,B在C的前面,所以在上一段程式碼中,c.foo()呼叫的是B.foo()而不是A.foo().

之所以B在C的前面,是因為在指定C的父類時先指定了B:

class C(B,A):

若將它改成:

class C(A,B):

c.foo()執行的就是A.foo()了.

熟悉環境變數的可以將__mro__理解為以目標物件為環境的PATH變數: 從左到右開始查詢,找到就執行,然後返回結果.

方法解析順序

從C.__mro__的值可以看出,Python的方法解析優先順序從高到低為:

1. 例項本身(instance)

2. 類(class)

3. super class,繼承關係越近,越先定義,優先順序越高.

其實屬性解析順序也基本一致,只不過多了個__getattr__的查詢(見Python物件的屬性訪問過程).

補充知識:python中的單繼承,多繼承和mro順序

python作為一門動態語言,是和c++一樣支援面向物件程式設計的。相對物件程式設計有三大特性,分別是繼承,封裝和多型。今天我們重點講解的是,python語言中的單繼承和多繼承。

繼承概念:

如果一個類繼承了另外一個類時,它將自動獲得另一個類的所有屬性和方法,那麼原有的類稱為父類,而新類稱為子類。子類繼承了其父類的所有屬性和方法。同時還可以定義自己的屬性和方法。
單繼承就是一個子類只能繼承一個父類。

格式: class 子類(父類)

舉例: class A(B)

A類擁有了B類的所有的特徵,A類繼承了B類

B類 父類,基類

A類 子類 派生類 後代類

繼承的作用:功能的升級和擴充套件
功能的升級就是對原有 的功能進行完善重新,功能的擴充套件就是對原本沒有的功能進行新增。減少程式碼的冗餘。

下面我們舉一個單繼承的例子:

class Dog(): #父類
 def __init__(self): #父類的屬性初始化
 self.name='狗'
 self.leg=4
 def __str__(self):
 return "名字:%s %d 條腿"%(self.name,self.leg)

class Taidi(Dog): #定義一個Taidi 泰迪 類繼承自Dog類 -->單繼承
 pass

taidi=Taidi()
print(taidi) 輸出結果--> 名字:狗 4 條腿

多繼承:

多繼承就是一個子類同時繼承自多個父類,又稱菱形繼承、鑽石繼承。

首先,我們先講多繼承中一個常見方法,單獨呼叫父類的方法。在子類初始化的時候需要手動呼叫父類的初始化方法進行父類的屬性的構造,不然就不能使用提供的屬性。

在子類中呼叫父類的初始化方法格式就是: 父類名._init_(self)

下面舉一個單獨呼叫父類方法的例子:

print("******多繼承使用類名.__init__ 發生的狀態******")
class Parent(object): #父類
 def __init__(self,name):
 print('parent的init開始被呼叫')
 self.name = name #屬性的初始化
 print('parent的init結束被呼叫')

class Son1(Parent): #單繼承 Son1子類繼承父類
 def __init__(self,name,age):
 print('Son1的init開始被呼叫')
 self.age = age
 Parent.__init__(self,name) #單獨呼叫父類的屬性
 print('Son1的init結束被呼叫')

class Son2(Parent): #也是單繼承 Son2繼承父類
 def __init__(self,gender):
 print('Son2的init開始被呼叫')
 self.gender = gender #單獨呼叫父類的初始化屬性方法
 Parent.__init__(self,name)
 print('Son2的init結束被呼叫')

class Grandson(Son1,Son2): #多繼承,繼承兩個父類
 def __init__(self,age,gender):
 print('Grandson的init開始被呼叫')
 Son1.__init__(self,age) # 單獨呼叫父類的初始化方法
 Son2.__init__(self,gender)
 print('Grandson的init結束被呼叫')

gs = Grandson('grandson',18,'男') #例項化物件
print('姓名:',gs.name)
print('年齡:',gs.age)
print('性別:',gs.gender)

print("******多繼承使用類名.__init__ 發生的狀態******\n\n")

下面讓我們看看執行的結果:

******多繼承使用類名.__init__ 發生的狀態******
Grandson的init開始被呼叫
Son1的init開始被呼叫
parent的init開始被呼叫
parent的init結束被呼叫
Son1的init結束被呼叫
Son2的init開始被呼叫
parent的init開始被呼叫
parent的init結束被呼叫
Son2的init結束被呼叫
Grandson的init結束被呼叫
姓名: grandson
年齡: 18
性別: 男
******多繼承使用類名.__init__ 發生的狀態******

mro順序

檢視上面的執行結果,我們發現由於多繼承情況,parent類被的屬性被構造了兩次,如果在更加複雜的結構下可能更加嚴重。

為了解決這個問題,Python官方採用了一個演算法將複雜結構上所有的類全部都對映到一個線性順序上,而根據這個順序就能夠保證所有的類都會被構造一次。這個順序就是MRO順序。

格式:

類名._mro_()

類名.mro()

多繼承中super呼叫有所父類的被重寫的方法

super本質上就是使用MRO這個順序去呼叫 當前類在MRO順序中下一個類。 super().init()則呼叫了下一個類的初始化方法進行構造。

print("******多繼承使用super().__init__ 發生的狀態******")
class Parent(object):
 def __init__(self,*args,**kwargs): # 為避免多繼承報錯,使用不定長引數,接受引數
 print('parent的init開始被呼叫')
 self.name = name
 print('parent的init結束被呼叫')

class Son1(Parent):
 def __init__(self,**kwargs): # 為避免多繼承報錯,使用不定長引數,接受引數
 print('Son1的init開始被呼叫')
 self.age = age
 super().__init__(name,**kwargs) # 為避免多繼承報錯,使用不定長引數,接受引數
 print('Son1的init結束被呼叫')

class Son2(Parent):
 def __init__(self,gender,**kwargs): # 為避免多繼承報錯,使用不定長引數,接受引數
 print('Son2的init開始被呼叫')
 self.gender = gender
 super().__init__(name,**kwargs) # 為避免多繼承報錯,使用不定長引數,接受引數
 print('Son2的init結束被呼叫')

class Grandson(Son1,Son2):
 def __init__(self,gender):
 print('Grandson的init開始被呼叫')
 # 多繼承時,相對於使用類名.__init__方法,要把每個父類全部寫一遍
 # 而super只用一句話,執行了全部父類的方法,這也是為何多繼承需要全部傳參的一個原因
 # super(Grandson,self).__init__(name,gender)
 super().__init__(name,gender)
 print('Grandson的init結束被呼叫')

print(Grandson.__mro__)

gs = Grandson('grandson','男')
print('姓名:',gs.gender)
print("******多繼承使用super().__init__ 發生的狀態******\n\n")

檢視下執行結果:

******多繼承使用super().__init__ 發生的狀態******
(<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
年齡: 18
性別: 男
******多繼承使用super().__init__ 發生的狀態******

單繼承中super

print("******單繼承使用super().__init__ 發生的狀態******")
class Parent(object):
 def __init__(self,name):
 print('parent的init開始被呼叫')
 self.name = name
 print('parent的init結束被呼叫')

class Son1(Parent):
 def __init__(self,age):
 print('Son1的init開始被呼叫')
 self.age = age
 super().__init__(name) # 單繼承不能提供全部引數
 print('Son1的init結束被呼叫')

class Grandson(Son1):
 def __init__(self,gender):
 print('Grandson的init開始被呼叫')
 super().__init__(name,age) # 單繼承不能提供全部引數
 print('Grandson的init結束被呼叫')

gs = Grandson('grandson',12,gs.age)
#print('性別:',gs.gender)
print("******單繼承使用super().__init__ 發生的狀態******\n\n")

執行結果:

******單繼承使用super().__init__ 發生的狀態******
Grandson的init開始被呼叫
Son1的init開始被呼叫
parent的init開始被呼叫
parent的init結束被呼叫
Son1的init結束被呼叫
Grandson的init結束被呼叫
姓名: grandson
年齡: 12
******單繼承使用super().__init__ 發生的狀態******

下面讓我們總結下:

MRO保證了多繼承情況 每個類只出現一次

super().__init__相對於類名.init,在單繼承上用法基本無差

但在多繼承上有區別,super方法能保證每個父類的方法只會執行一次,而使用類名的方法會導致方法被執行多次

多繼承時,使用super方法,對父類的傳引數,應該是由於python中super的演算法導致的原因,必須把引數全部傳遞,否則會報錯

單繼承時,使用super方法,則不能全部傳遞,只能傳父類方法所需的引數,否則會報錯

多繼承時,相對於使用類名.__init__方法,要把每個父類全部寫一遍,而使用super方法,只需寫一句話便執行了全部父類的方法,這也是為何多繼承需要全部傳參的一個原因

下面是一個簡答的面試題:

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,Child2.x)
Parent.x = 3
print(Parent.x,Child2.x)

執行結果:

1 1 1
1 2 1
3 2 3

以上這篇淺談Python的方法解析順序(MRO)就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。