Python - super() 函式與 MRO 詳解
Posted on Oct 6, 2023 in Python 程式設計 - 中階 by Amo Chen ‐ 6 min read
Python 的物件導向程式設計(OOP)有 2 個一定要懂的東西:
- super() 函式
- MRO(Method Resolution Order) / 方法解析順序
如果不懂得這 2 個東西,就無法徹底解放類別(class)的力量,甚至可能導致寫出不夠彈性而且冗長的程式碼。
super() + MRO = 超級瑪利歐?(誤
本文將從 super() 函式開始講解,說明 Python 的 MRO(Method Resolution Order) ,並介紹 MRO 的特性在實務上的應用。
如果你無法正確回答以下範例結果的執行結果,那麽推薦你看完本文:
class Parent(object):
NAME = 'Parent'
def __str__(self):
return self.NAME
class Child(Parent):
NAME = 'Child'
def __str__(self):
return super().__str__()
c = Child()
print(c)
正確答案為: Child
本文環境
- Python 3
super() 函式介紹
我們都知道物件導向程式設計的重要特點之一是「繼承(Inheritance)」。
透過繼承,我們可以部分改寫(override)父類別的某些屬性、方法,提高程式碼的再利用率與彈性,例如下列 Parent 類別,該類別僅提供一個方法 get_inheritance() 列印其繼承關係:
class Parent(object):
def get_inheritance(self):
return 'Parent'
如果我們有另 1 個 Child 類別繼承 Parent , 如果要讓 Child 類別正確列印其繼承關係,也就是要讓 get_inheritance() 正確運作的話,那麼我們最毫無技巧的作法就是重寫一遍 get_inheritance() 方法,在裡面寫出 Parent <- Child , 代表 Child 類別繼承 Parent 類別(如下圖):
class Child(Parent):
def get_inheritance(self):
return 'Parent <- Child'

萬一有很多類別繼承 Parent 類別,或是繼承 Child 類別甚至是 Child 的子類別呢?前述的做法肯定會讓你寫一堆額外的程式碼,甚至缺乏彈性,改一個類別要跟著改其他相關的類別。
那麼,有沒有更好的辦法?
首先看到前述範例的字串 Parent <- Child 中的 Parent ,該字串很明顯來自父類別所定義的方法,如果我們可以在子類別呼叫父類別的方法,那麼 Parent 字串就不用寫死,子類別的方法就寫得可以更彈性。
所以 Python 提供 super() 函式讓我們可以在子類別中呼叫父類別裡定義的方法(即使子類別裡有同名方法,後續會說明為什麼能做到),例如下列範例中的 super().get_inheritance() , super() 回傳一個 proxy object, 當我們試圖呼叫 1 個方法,它就會代替我們從繼承關係開始向上尋找,直到找到相對應的方法:
class Child(Parent):
def get_inheritance(self):
parent = super().get_inheritance()
return ' <- '.join([parent, 'Child'])
c = Child()
print(c.get_inheritance())
所以上述範例,當我們呼叫 super().get_inheritance() 時,它就會到 Parent 類別尋找有沒有 get_inheritance() 這個方法可以呼叫,並回傳其結果,上述範例執行結果如下,可以看到我們正確呼叫父類別裡的 get_inheritance() ,而且也列印出其正確的結果:
Parent <- Child
如果是 3 layers 的繼承關係,也可以善用 super() 讓每 1 層只要加一點工,例如下列範例同樣在 GrandChild 類別中改寫 get_inheritance() 方法,加上自己獨有的部分即可:
class Parent(object):
def get_inheritance(self):
return 'Parent'
class Child(Parent):
def get_inheritance(self):
parent = super().get_inheritance()
return ' <- '.join([parent, 'Child'])
class GrandChild(Child):
def get_inheritance(self):
parent = super().get_inheritance()
return ' <- '.join([parent, 'GrandChild'])
c = GrandChild()
print(c.get_inheritance())
上述範例執行結果如下,從結果可以看到即使到第 3 層繼承,都不用明確寫明 parent 的值,就能夠把每 1 層的關係在 get_inheritance() 中列印出來:
Parent <- Child <- GrandChild
其原因在於 GrandChild 的 super().get_inheritance() 會呼叫 Child 類別的 get_inheritance() 方法,而 Child 類別的 super().get_inheritance() 又會去呼叫 Parent 類別的 get_inheritance() 方法,最後這些值會串起來,變成上述的結果,如下圖所示。

super() 函式的應用
看到此處,大家應該可以想到 super() 函式很適合用在再包裝(wrapper)的用途上,例如對父類別的方法執行結果做一些加工,變成新的結果!
例如 Icon 類別,當我們想要讓 Icon 也可以有文字提示時,就用 1 個新類別 IconWithText 搭配 super() 函式進行改寫:
class Icon:
def __str__(self):
return '<i class="fa-solid fa-check"></i>'
class IconWithText(Icon):
def __init__(self, text):
self.text = text
def __str__(self):
return super().__str__() + f'<span>{self.text}</span>'
print(IconWithText('Hello'))
上述範例執行結果如下:
<i class="fa-solid fa-check"></i><span>Hello</span>
這種運用 super() 四兩撥千斤類似的用法在 Python OOP 很常見!
super() 函式的誤區
回到文章開頭的範例,應該不少人疑問為什麼正確答案是 Child :
class Parent(object):
NAME = 'Parent'
def __str__(self):
return self.NAME
class Child(Parent):
NAME = 'Child'
def __str__(self):
return super().__str__()
c = Child()
print(c) # Child
當我們在子類別透過 super() 呼叫父類別的方法時,它會有 1 個解析的過程,會先從最近的父類別開始找相對應的方法,如果有的話,就完成解析,如果沒有就往父類別的父類別一路找上去。
但是當執行到父類別的 return self.NAME 時,它這邊跟 super() 函式無關,而是 Attribute Lookup 或 Attribute Search 機制, Python 會先查看 Child 類別裡有沒有 NAME 屬性可以用,而 Child 類別剛好有定義 NAME 屬性,就直接拿來用,因此執行結果是 Child 。
不信的話,可以把 Child 類別的 NAME 屬性刪掉,執行結果就會變為 Parent 。
所以 super() 只管怎麼找到方法,至於找屬性則是另外的機制!
MRO(Method Resolution Order) / 方法解析順序
呼叫類別裡的方法時,解析的過程會從子類別往父類別往上找,如下列範例會找到 A 類別的 run():
class A:
def run(self):
print('run')
class B(A):
def __init__(self):
self.run()
B()
不過 Python 是允許多重繼承的,也就是 1 個類別可以繼承多個類別,這種情況下就需要瞭解其解析的規則,這規則稱為 MRO (Method Resolution Order) ,或稱方法解析順序。
所以接下來,談談 MRO 。
首先,以下是 1 個經典的繼承模型:
class A(object):
def method(self):
return 'Class A'
class B(A):
def method(self):
return 'Class B'
class C(A):
def method(self):
return 'Class C'
class D(B, C):
def method(self):
m = super().method()
print(m)
return 'Class D'
d = D()
d.method()
上述範例執行結果如下:
Class B
繼承關係畫成圖的話,如下所示:

從範例程式的執行結果可以看到 D 類別就算同時繼承 B 與 C 類別,最後也只有執行到 B 類別定義的方法而已,這邊可以簡單理解為當繼承多個類別時,解析的順序會從左到右開始。
正常的 MRO 解析過程,在不使用 super() 函式的情況下,例如 class D(B, C) 就會從 D 類別自己開始先找,接著找 B 類別,再來找 C 類別,找到就結束尋找,如果都找不到就再往上一層找。
上述範例則是使用 super() 函式的結果,其解析過程是略過 D 類別,先找 B 類別,再來找 C 類別。
但具體是怎麼回事?我們一步一步說來。
首先,當我們呼叫 super().method() 時,其實等同於呼叫:
super(D, self).method()
先談談 super(D, self) 的第 2 個參數 self ,第 2 個參數會提供給 super() 函式 1 個解析順序,也就是 MRO 。
想知道 MRO 的話,可以用以下 2 種方式取得:
<類別名稱>.__mro__<類別名稱>.mro()
例如:
>>> print(D.__mro__)
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
或
>>> print(D.mro())
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
可以看到 D 類別的 MRO 是 D → B → C → A → object 。
p.s. object 類別是 Python 所有類別之母,所以最後都是 object
super() 有了第 2 個參數,就知道 MRO 是什麼以及怎麼找方法。
至於 super() 第 1 個參數則代表要從 MRO 的哪裡開始找,例如 super(D, self) 就代表要從 B → C → A → object 開始一路找上去,這就是為什麼即使 D 類別裡有個同名的方法時,也不會被執行的原因!
看到這裡,大家應該知道怎麼控制 super() 函式,讓它執行特定父類別的方法囉!也就是控制第 1 個參數,讓 super() 從 MRO 裡的特定位置開始找,舉下列述範例為例, D 類別的 MRO 為 D → B → C → A → object, 在不控制 super() 函式的情況下,如果呼叫 super().method() 會從 B → C → A → object 順藤摸瓜尋找 method() 方法,由於 C 類別有定義 method() , 所以最後執行結果為 Class C :
class A(object):
def method(self):
return 'Class A'
class B(A): pass
class C(A):
def method(self):
return 'Class C'
class D(B, C):
def method(self):
m = super().method()
print(m)
return 'Class D'
d = D()
d.method() # result: Class C
如果我們想要讓 super() 去呼叫 A 類別的方法,只要將 super() 改為 super(C, self) 即可,因為它的解析就會變成 A → object :
class A(object):
def method(self):
return 'Class A'
class B(A): pass
class C(A):
def method(self):
return 'Class C'
class D(B, C):
def method(self):
m = super(C, self).method()
print(m)
return 'Class D'
d = D()
d.method() # Class A
大家可以試看看上述程式碼!
多重繼承與 MRO 的實務應用
如果能正確理解 MRO 的觀念,加上 Python 的多重繼承,就能夠設計稱為 Mixin 的類別,這種類別特點就像樂高一樣,可以用多重繼承的方式賦予類別各式各樣的功能,舉下列範例為例,該範例設計 2 個 Mixin 類別,分別是 FlyMixin 與 DiveMixin , SuperSubmarine 類別則透過多重繼承這 2 個 Mixin 獲得飛行(fly)與潛水(dive)的能力:
class FlyMixin(object):
def fly(self):
print('Fly!')
class DiveMixin(object):
def dive(self):
print('Dive!')
class SuperSubmarine(FlyMixin, DiveMixin): pass
s = SuperSubmarine()
s.fly()
s.dive()
上述範例執行結果如下:
Fly!
Dive!
因為 MRO, 所以 SuperSubmarine 在呼叫 fly() 時,會先找到 FlyMixin 裡的 fly(), 在呼叫 dive() 時,則因為 FlyMixin 沒有定義 dive() ,所以往後找到 DiveMixin 裡的 dive() 。
實務上,Django 的 View 就提供很多 Mixin 類別可以使用,其原理就是如此簡單。
同場加映——先呼叫 super().__init__() 還是後呼叫 super().__init__() ?
下列是先呼叫 super().__init__() 的範例,可以看到 B().name 是正確的 b :
class A:
def __init__(self):
self.name = 'a'
class B(A):
def __init__(self):
super().__init__()
self.name = 'b'
print(B().name) # b
下列是後呼叫 super().__init__() 的範例,可以看到 B().name 是不正確的 a :
class A:
def __init__(self):
self.name = 'a'
class B(A):
def __init__(self):
self.name = 'b'
super().__init__()
print(B().name)
這樣先後呼叫的區別,也會造成執行結果不同,有時候腦袋不清楚,就會沒注意到,要特別注意這種情況!
總結
super() 函式的運用是邁向高手的重要關卡,在此之前正確理解 MRO 更是絕對要做的基本功,結合 2 者,將可以在 Python OOP 世界裡更加自在。
以上!
Happy Coding!
References
Python’s super() considered super!
Django - Class-based views mixins