Python super 沒那麼簡單
說到super
, 大家可能覺得很簡單呀,不就是用來呼叫父類方法的嘛。如果真的這麼簡單的話也就不會有這篇文章了,且聽我細細道來。:smile:
約定
在開始之前我們來約定一下本文所使用的 Python 版本。預設用的是 Python 3,也就是說:本文所定義的類都是新式類。如果你用到是 Python 2 的話,記得繼承object
:
# 預設, Python 3 class A: pass # Python 2 class A(object): pass
Python 3 和 Python 2 的另一個區別是: Python 3 可以使用直接使用super().xxx
代替super(Class, self).xxx
:
# 預設,Python 3 class B(A): def add(self, x): super().add(x) # Python 2 class B(A): def add(self, x): super(B, self).add(x)
所以,你如果用的是 Python 2 的話,記得將本文的super()
替換為suepr(Class, self)
。
如果還有其他不相容 Python 2 的情況,我會在文中註明的。
單繼承
在單繼承中super
就像大家所想的那樣,主要是用來呼叫父類的方法的。
class A: def __init__(self): self.n = 2 def add(self, m): print('self is {0} @A.add'.format(self)) self.n += m class B(A): def __init__(self): self.n = 3 def add(self, m): print('self is {0} @B.add'.format(self)) super().add(m) self.n += 3
你覺得執行下面程式碼後,b.n
的值是多少呢?
b = B() b.add(2) print(b.n)
執行結果如下:
self is <__main__.B object at 0x106c49b38> @B.add self is <__main__.B object at 0x106c49b38> @A.add 8
這個結果說明了兩個問題:
-
super().add(m)
確實呼叫了父類 A 的add
方法。 -
super().add(m)
呼叫父類方法def add(self, m)
時, 此時父類中self
並不是父類的例項而是子類的例項, 所以b.add(2)
之後的結果是 5 而不是 4 。
不知道這個結果是否和你想到一樣呢?下面我們來看一個多繼承的例子。
多繼承
這次我們再定義一個class C
,一個class D
:
class C(A): def __init__(self): self.n = 4 def add(self, m): print('self is {0} @C.add'.format(self)) super().add(m) self.n += 4 class D(B, C): def __init__(self): self.n = 5 def add(self, m): print('self is {0} @D.add'.format(self)) super().add(m) self.n += 5
下面的程式碼又輸出啥呢?
d = D() d.add(2) print(d.n)
這次的輸出如下:
self is <__main__.D object at 0x10ce10e48> @D.add self is <__main__.D object at 0x10ce10e48> @B.add self is <__main__.D object at 0x10ce10e48> @C.add self is <__main__.D object at 0x10ce10e48> @A.add 19
你說對了嗎?你可能會認為上面程式碼的輸出類似:
self is <__main__.D object at 0x10ce10e48> @D.add self is <__main__.D object at 0x10ce10e48> @B.add self is <__main__.D object at 0x10ce10e48> @A.add 15
為什麼會跟預期的不一樣呢?下面我們將一起來看看super
的奧祕。
super 是個類
當我們呼叫super()
的時候,實際上是例項化了一個super
類。你沒看錯,super
是個類,既不是關鍵字也不是函式等其他資料結構:
>>> class A: pass ... >>> s = super(A) >>> type(s) <class 'super'> >>>
在大多數情況下,super
包含了兩個非常重要的資訊: 一個 MRO(Method Resolution Order) 列表以及 MRO 中的一個類。當以如下方式呼叫super
時:
super(a_type, obj)
MRO 列表指的是type(obj)
的 MRO 列表, MRO 中的那個類就是a_type
, 同時isinstance(obj, a_type) == True
。
當這樣呼叫時:
super(type1, type2)
MRO 指的是type2
的 MRO 列表, MRO 中的那個類就是type1
,同時issubclass(type2, type1) == True
。
那麼,super()
實際上做了啥呢?簡單來說就是:提供一個 MRO 列表以及一個 MRO 中的類 C ,super()
將返回一個從 MRO 列表中 C 之後的類中查詢方法的物件。
也就是說,查詢方式時不是像常規方法一樣從所有的 MRO 類中查詢,而是從 MRO 列表的 tail 中查詢。
舉個栗子, 有個 MRO 列表:
[A, B, C, D, E, object]
下面的呼叫:
super(C, A).foo()
super
只會從 C 之後查詢,即: 只會在 D 或 E 或object
中查詢foo
方法。
多繼承中 super 的工作方式
再回到前面的
d = D() d.add(2) print(d.n)
現在你可能已經有點眉目,為什麼輸出會是
self is <__main__.D object at 0x10ce10e48> @D.add self is <__main__.D object at 0x10ce10e48> @B.add self is <__main__.D object at 0x10ce10e48> @C.add self is <__main__.D object at 0x10ce10e48> @A.add 19
了吧 :wink:
下面我們來具體分析一下:
-
D 的 MRO 是:
[D, B, C, A, object]
。 備註: 可以通過D.mro()
(Python 2 使用D.__mro__
) 來檢視 D 的 MRO 資訊) - 詳細的程式碼分析如下:
class A: def __init__(self): self.n = 2 def add(self, m): # 第四步 # 來自 D.add 中的 super # self == d, self.n == d.n == 5 print('self is {0} @A.add'.format(self)) self.n += m # d.n == 7 class B(A): def __init__(self): self.n = 3 def add(self, m): # 第二步 # 來自 D.add 中的 super # self == d, self.n == d.n == 5 print('self is {0} @B.add'.format(self)) # 等價於 suepr(B, self).add(m) # self 的 MRO 是 [D, B, C, A, object] # 從 B 之後的 [C, A, object] 中查詢 add 方法 super().add(m) # 第六步 # d.n = 11 self.n += 3 # d.n = 14 class C(A): def __init__(self): self.n = 4 def add(self, m): # 第三步 # 來自 B.add 中的 super # self == d, self.n == d.n == 5 print('self is {0} @C.add'.format(self)) # 等價於 suepr(C, self).add(m) # self 的 MRO 是 [D, B, C, A, object] # 從 C 之後的 [A, object] 中查詢 add 方法 super().add(m) # 第五步 # d.n = 7 self.n += 4 # d.n = 11 class D(B, C): def __init__(self): self.n = 5 def add(self, m): # 第一步 print('self is {0} @D.add'.format(self)) # 等價於 super(D, self).add(m) # self 的 MRO 是 [D, B, C, A, object] # 從 D 之後的 [B, C, A, object] 中查詢 add 方法 super().add(m) # 第七步 # d.n = 14 self.n += 5 # self.n = 19 d = D() d.add(2) print(d.n)
呼叫過程圖如下:
D.mro() == [D, B, C, A, object] d = D() d.n == 5 d.add(2) class D(B, C):class B(A):class C(A):class A: def add(self, m):def add(self, m):def add(self, m):def add(self, m): super().add(m)1.--->super().add(m) 2.--->super().add(m)3.--->self.n += m self.n += 5<------6. self.n += 3<----5. self.n += 4<----4. <--| (14+5=19)(11+3=14)(7+4=11)(5+2=7)
現在你知道為什麼d.add(2)
後d.n
的值是 19 了吧 :wink:
That’s all! 希望這篇文章能對你有所幫助 :wink:
原文連結:ofollow,noindex" target="_blank">https://mozillazg.com/2016/12/python-super-is-not-as-simple-as-you-thought.html