1. 為什么只說方法不說屬性
關于“子類對象如何訪問父類的同名屬性“是沒有意義的。因為父類的屬性子類都有,子類還有父類沒有的屬性,在初始化時,給子類對象具體化所有的給定屬性,完全沒必要訪問父類的屬性,因為是一樣的。而訪問同名方法就不一樣了,因為子類會重寫父類的同名函數。
2、子類訪問父類的兩種方法
假設Base為基類
class Base(object):
def __init__(self):
print “Base init”
2.1 普通方法(調用父類的未綁定的構造方法)
在調用一個對象的方法時,該方法的self參數會被自動綁定到對象上(稱為綁定方法)。但如果直接調用類的方法(比如A.__init),那么就沒有實例會被綁定。這樣就可以自由的提供需要的self參數,這種方法稱為未綁定方法。
class Leaf(Base):
def __init__(self):
Base.__init__(self)
print ("Leaf init")
2.2 super方法
class Leaf(Base):
def __init__(self):
super(Leaf, self).__init__() # 在單繼承中等價于super().__init__()
print “Leaf init”
2.3 上述兩個方法的結果
>>>leaf = Leaf()
"Base init"
"Leaf init"
在Python3中的類都是新式類,廣度優先的查找順序,在定義一個類時就會生成一個MRO列表(經典類沒有MRO列表,深度優先),查找順序就是按照這個列表中的類的順序從左到右進行的。
3、鉆石繼承中的問題
”鉆石繼承“指的是一個子類繼承自多個父類,成鉆石形狀。
3.1 如何解決鉆石繼承中父類被多次初始化
C++使用虛擬繼承來解決鉆石繼承問題;Java禁止使用多繼承;Ruby禁止使用多繼承;Python和C++一樣,支持多繼承的語法。但Python的解決思路和C++完全不一樣,Python是的用就是super。所以,我們有必要來詳細探討super的機制。
我把第二章的代碼用super重寫一下,使用如下的繼承關系
class Base(object):
def __init__(self):
print ("Base init")
class Medium1(Base):
def __init__(self):
super(Medium1, self).__init__()
print ("Medium1 init")
class Medium2(Base):
def __init__(self):
super(Medium2, self).__init__()
print ("Medium2 init")
class Leaf(Medium1, Medium2):
def __init__(self):
super(Leaf, self).__init__()
print ("Leaf init")
>>> leaf = Leaf()
"Base init"
"Medium2 init"
"Medium1 init"
"Leaf init"
可以看到整個初始化過程符合我們的預期,Base只被初始化了1次。而且重要的是,相比原來的普通寫法,super方法并沒有寫額外的代碼,也沒有引入額外的概念.
3.2 super的內核:MRO
要理解super的原理,就要先了解mro。mro是method resolution order的縮寫,表示了類繼承體系中的成員解析順序。
在python中,每個類都有一個mro的類方法。我們來看一下鉆石繼承中,Leaf類的mro是什么樣子的:
>>>Leaf.mro()
[Leaf, Medium1, Medium2, Base]
可以看到mro方法返回的是一個祖先類的列表。Leaf的每個祖先都在其中出現一次,這也是super在父類中查找成員的順序。
通過mro,python巧妙地將多繼承的圖結構,轉變為list的順序結構。super在繼承體系中向上的查找過程,變成了在mro中向右的線性查找過程,任何類都只會被處理一次。
通過這個方法,python解決了多繼承中的2大難題:
查找順序問題。從Leaf的mro順序可以看出,如果Leaf類通過super來訪問父類成員,那么Medium1的成員會在Medium2之前被首先訪問到。如果Medium1和Medium2都沒有找到,最后再到Base中查找。
鉆石繼承的多次初始化問題。在mro的list中,Base類只出現了一次。事實上任何類都只會在mro list中出現一次。這就確保了super向上調用的過程中,任何祖先類的方法都只會被執行一次。
至于mro的生成算法,可以參考這篇wiki:C3算法
3.3 super的具體用法
首先看一下super的官方文檔,重點講第一種和第三種的用法,因為第二種用處不大
Help on class super in module __builtin__:
class super(object)
| super(type, obj) -> bound super object; requires isinstance(obj, type)
| super(type) -> unbound super object
| super(type, type2) -> bound super object; requires issubclass(type2, type)
3.2.1 super(type,obj)
這種寫法要從右往左的分析參數:obj告訴我們self綁定的對象所屬類的MRO順序,type告訴我們從MRO順序的哪個位置開始找
當我們在Leaf的init中這樣寫super時,super(Leaf, self).init()的意思是說:
獲取self所屬類的mro, 也就是[Leaf, Medium1, Medium2, Base]
從mro中Leaf右邊的一個類開始,依次尋找__init__函數。這里是從Medium1開始尋找
一旦找到,就把找到的__init__函數綁定到self對象,并返回
class Leaf(Medium1, Medium2):
def __init__(self):
super(Leaf, self).__init__()
print “Leaf init”
從這個執行流程可以看到,如果我們不想調用Medium1的__init__,而想要調用Medium2的__init__,那么super應該寫成:super(Medium1, self).__ init __()
3.2.2 super(type,type2)
當我們在Leaf中寫類方法的super時:
class Leaf(Medium1, Medium2):
def __new__(cls):
obj = super(Leaf, cls).__new__(cls)
print “Leaf new”
return obj
super(Leaf, cls).new(cls)的意思是說:
獲取cls這個類的mro,這里也是[Leaf, Medium1, Medium2, Base]
從mro中Leaf右邊的一個類開始,依次尋找__new__函數
一旦找到,就返回“非綁定”的__new__函數
3.2.3 super一些補充的知識點
super實現原理:通過c3算法,生成mro(method resolution order)列表,根據列表中元素順序查詢調用。新式類調用順序為廣度優先,舊式類為深度優先,可以通過__mro__來查看。
python2沒有默認繼承object;python3默認全部繼承自object類,都是新式類
如果子類繼承了多個父類,它只需要使用一次super函數就可以
4. 子類如何訪問父類的同名方法
講了這么多,把上面的知識點綜合起來,我們就可以解決子類如何訪問父類的同名方法。
看下面的代碼:
class A:
def f_a(self):
print('--------A--------')
class B:
def f_a(self):
print('-------B-------')
class C(A,B):
def f_a(self):
print('--------C--------')
c = C()
c.f_a()
》》》--------C--------
如果我們想在C類中的f_a方法里面使用父類A或者父類B的方法該如何操作呢
方法有兩種:
4.1 方法一:調用父類的未綁定方法
class C(A,B):
def f_a(self):
A.f_a(self)
B.f_a(self)
print('--------C--------')
c = C()
c.f_a()
》》》--------A--------
》》》-------B-------
》》》--------C--------
這里調用父類的f_a方法時括號里面要寫self,表明這是一個類調用。
缺點:比如說如果修改了父類的名稱,那么在子類中會涉及多出修改,并且python是允許多繼承的語言,上述方法在多繼承時就要重復寫多次,顯得累贅,為了解決這些問題,python引進了super()機制。
4.2 super機制
class C(A,B):
def f_a(self):
super().f_a()
print('--------C--------')
c = C()
c.f_a()
》》》--------A--------
》》》--------C--------
這里直接使用super()方法會調用A類的f_a方法,因為它會默認多繼承中從左到右的順序來調用,那么有人就會問了,假如我想調用B類中的f_a方法是不是把A和B對調下呢,這種方法也行,不過這不算的上是一種較為巧妙的方法。
我們還可以使用super()方法,但要對其修改下:
class C(A,B):
def f_a(self):
super(C,self).f_a()
super(A,self).f_a()
print('--------C--------')
c = C()
c.f_a()
》》》--------A--------
》》》-------B-------
》》》--------C--------
super(C,self).f_a()會調用最左邊的(即A類)的f_a方法,而super(A,self).f_a()會調用A類后面那個類的f_a()方法,若果繼承的不止兩個類,如果要調用某個類方法,只要知道前一個類名就可以調用。