Python多重繼承說明及應用

Python中沒有接口的概念,替代之的是多重繼承。而引入多重繼承之後,一個新的問題是多個父類之間的繼承順序和方法調用順序。比如:子類中調用的方法在多個父類中都存在的時候,到底會調用哪個父類的方法?(顯然不會把所有父類的方法都調用一遍)


理解MRO

如果把單重繼承理解爲是鏈表結構,那麼多重繼承則可以認爲是樹狀結構。所以多重繼承的向上搜索的規則有2種實現方式:
  1. 深度優先搜索(DFS)
  2. 廣度優先搜索(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__方法,則需要自己顯式的初始化父類方法。

多重繼承應用





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章