理解MRO
如果把單重繼承理解爲是鏈表結構,那麼多重繼承則可以認爲是樹狀結構。所以多重繼承的向上搜索的規則有2種實現方式:- 深度優先搜索(DFS)
- 廣度優先搜索(BFS)
深度優先搜索圖解如下:
這種查找方式的缺點:
棱形繼承時可能會出現父類訪問優先於子類訪問。如上圖中的父類D就優於子類C先被訪問到。這會導致子類C中的覆蓋方法無法被訪問到。
廣度優先搜索圖解如下:
這種查找方式的缺點:
在正常繼承時沒有按照繼承的單調性來查找。
爲了解決DFS和BFS的缺陷問題,Python中使用了C3算法,經過調整之後多重繼承的查找方式如下:
C3算法結合了DFS和BFS的優點,當正常繼承時使用DFS,當爲棱形繼承時則使用BFS(DFS是Python2.2以前的算法,Python2.2同時使用了DFS(經典類)、BFS算法(新式類),Python2.3及之後使用C3算法)。最後推測下圖中MRO的順序爲何?
父類方法顯式調用
Python2.7版本中,類的定義有2種方式:
- 經典類
- 新式類
經典類是Python一直都存在的,而新式類是Python2.2中才加入的,而Python3.0中只支持新式類。它們的各自定義如下:
#!/usr/bin/env python
#coding:utf-8
class oldClass: #經典類
pass
class newClass(object): #新式類
pass
print type(oldClass) #classobj
print type(newClass) #type
從代碼可以知道顯式的繼承自object的則爲新式類,不繼承任何其它類的爲經典類。經典類與新式類除了定義上有區別,在顯式調用父類方法時也有些許不同。經典類調用父類代碼如下:
#!/usr/bin/python
#encoding: utf-8
import inspect
class oldBase(object):
def __init__(self):
print '%s' % oldBase.__name__
class oldParentA(oldBase):
def __init__(self):
print '%s' % oldParentA.__name__
oldBase.__init__(self)
class oldParentB(oldBase):
def __init__(self):
print '%s' % oldParentB.__name__
oldBase.__init__(self)
class oldChild(oldParentA, oldParentB):
def __init__(self):
print '%s' % oldChild.__name__
oldParentA.__init__(self)
oldParentB.__init__(self)
if __name__ == '__main__':
print inspect.getmro(oldChild) #打印mro中父類的查找順序
n = oldChild()
新式類也可以使用經典類的方式調用父類方法,另外還可以使用super類來實現。具體如下:
#!/usr/bin/python
#encoding: utf-8
class newBase(object):
def __init__(self):
print '%s' % newBase.__name__
class newParentA(newBase):
def __init__(self):
print '%s' % newParentA.__name__
super(newParentA, self).__init__()
class newParentB(newBase):
def __init__(self):
print '%s' % newParentB.__name__
super(newParentB, self).__init__()
class newChild(newParentA, newParentB):
def __init__(self):
print '%s' % newChild.__name__
super(newChild, self).__init__()
if __name__ == '__main__':
print newChild.__mro__ #打印mro中父類的查找順序
n = newChild()
之所以給新式類提供了super類來調用父類的方法,是因爲前面介紹的棱形繼承的場景。如果使用經典類的方式調用,則公共父類*Base會被調用兩次;而如果使用super類的方式則只會調用一次。
另外,在類創建時默認不創建__init__方法時,經典類與新式類的行爲也不一樣。經典會只調用第一個父類的__init__方法,而新式類與顯式使用super的效果是一樣的。也就是說如果你使用的是新式類,那麼不是初始化__init__也會初始化所有的父類方法。而一旦你定義了__init__方法,則需要自己顯式的初始化父類方法。