前置知識

為什麼要用 super()

當子類重寫了父類方法時,又想呼叫父類的同名方法時,就需要用到 super()

什麼是 super

  • 在 Python 中,super 是一個特殊的類
  • super() 就是使用 super 類創建出來的物件
  • 實際應用的場景:子類在重寫父類方法時,呼叫父類方法

單繼承中使用 super

例項方法使用 super

類圖

實際程式碼

class A:
def __init__(self):
self.n = 1 def add(self, m):
print(f'AAA [self] is {id(self)}')
print(f'AAA [self.n] is {self.n}')
self.n += m class B(A):
def __init__(self):
self.n = 100 # 重寫父類方法
def add(self, m):
# 子類特有程式碼
print(f'BBB [self] is {id(self)}')
print(f'BBB [self.n] is {self.n}') # 呼叫父類方法
super().add(m) self.n += m b = B()
b.add(2)
print(b.n) # 輸出結果
BBB [self] is 4489158560
BBB [self.n] is 100 AAA [self] is 4489158560
AAA [self.n] is 100 104
  1. super().add()  的確呼叫了父類方法
  2. 重點:此時父類方法的 self 並不是父類例項物件,而是子類例項物件

構造方法使用 super

class Animal:
def __init__(self, name):
self.name = name def prints(self):
print("Animale name is ", self.name) class Dog(Animal):
def __init__(self, name, age):
# 呼叫父類的 init 構造方法
super(Dog, self).__init__(name)
self.age = age def prints(self):
# 呼叫父類的方法
super(Dog, self).prints()
print("Dog age is ", self.age) dog = Dog("小汪", 10)
dog.prints() # 輸出結果
Animale name is 小汪
Dog age is 10

這裡用了 super(子類名, self) ,和上面的 super() 是一樣效果

呼叫父類方法有兩種方式

  • super().父類方法()
  • super(子類名, self).父類方法()

其實還有第三種

在 Python  2.x 的時候,如果需要呼叫父類的方法,還可以用

父類名.方法(self)
  • 這種方式,Python 3.x 還是支援的
  • 不過不推薦,因為父類名發生變化的話,方法呼叫位置的類名也要同步修改

通過父類名呼叫父類方法(不推薦)

class Animal:
def __init__(self, name):
self.name = name def prints(self):
print("Animale name is ", self.name) class Dog(Animal):
def __init__(self, name, age):
# 呼叫父類的 init 構造方法
Animal.__init__(self, name)
self.age = age def prints(self):
# 呼叫父類的方法
Animal.prints(self)
print("Dog age is ", self.age) dog = Dog("小汪", 10)
dog.prints() # 輸出結果
Animale name is 小汪
Dog age is 10

通過父類名呼叫的這種方式,是需要傳 self 引數的哦

溫馨提示

在開發時, 父類名.方法() , super().方法() 兩種方式不要混用哈

靈魂拷問一:既然已經重寫了子類的構造方法,為什麼還要去呼叫 super?

子類需要重寫父類方法來實現子類獨有的功能,但同時又需要依賴父類方法來完成某些邏輯

實際栗子

  • 在實現多執行緒的時候(後面會詳細展開說多執行緒)
  • 父類 Thread 的構造方法包含了很多邏輯程式碼
  • 子執行緒雖然需要實現子類獨有功能,但仍需父類方法來處理其他邏輯

from threading import Thread

class MyThread(Thread):
def __init__(self, name):
# 1、實現子類獨有功能
print("子類執行緒 %s" % name)
# 2、需要依賴父類方法完成其他功能
super().__init__(name=name)

多繼承中使用 super

類圖

實際程式碼

# 多繼承
class Animal:
def __init__(self, animalName):
print(animalName, 'is an animal.') # Mammal 繼承 Animal
class Mammal(Animal):
def __init__(self, mammalName):
print(mammalName, 'is a mammal.')
super().__init__(mammalName) # CannotFly 繼承 Mammal
class CannotFly(Mammal):
def __init__(self, mammalThatCantFly):
print(mammalThatCantFly, "cannot fly.")
super().__init__(mammalThatCantFly) # CannotSwim 繼承 Mammal
class CannotSwim(Mammal):
def __init__(self, mammalThatCantSwim):
print(mammalThatCantSwim, "cannot swim.")
super().__init__(mammalThatCantSwim) # Cat 繼承 CannotSwim 和 CannotFly
class Cat(CannotSwim, CannotFly):
def __init__(self):
print('I am a cat.');
super().__init__('Cat') # Driver code
cat = Cat()
print('')
bat = CannotSwim('Bat') # 輸出結果
I am a cat.
Cat cannot swim.
Cat cannot fly.
Cat is a mammal.
Cat is an animal. Bat cannot swim.
Bat is a mammal.
Bat is an animal.

好像挺奇怪的,從輸出結果看,為什麼 CannotSwim 類裡面的 super().__init__() 呼叫的是 CannotFly 類裡面的方法呢?不是應該呼叫 CannotSwim 的父類 Mamal 的方法嗎?

靈魂拷問二:super 的執行順序到底是什麼?

  • 其實 super() 並不一定呼叫父類的方法
  • super() 是根據類的 MRO 方法搜尋順序來決定呼叫誰的
  • super() 真正呼叫的是 MRO 中的下一個類,而不一定是父類
  • 當然,這種情況只會出現在多繼承

先來看看 Cat 的 MRO

print(Cat.__mro__)

(<class '__main__.Cat'>, <class '__main__.CannotSwim'>, <class '__main__.CannotFly'>, <class '__main__.Mammal'>, <class '__main__.Animal'>, <class 'object'>)

從 Cat 的 MRO 可以看到

  • CannotSwim 後面跟的是 CannotFly 而不是 Mamal
  • 所以 CannotSwim 類裡面的 super() 會呼叫 CannotFly 裡面的方法

多繼承的栗子二

實際程式碼

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} @AAA.add'.format(self))
self.n += m
# d.n == 7 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} @CCC.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 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} @BBB.add'.format(self))
# self 的 MRO 是 [D, B, C, A, object]
# 從 B 之後的 [C, A, object] 中查詢 add 方法
# 從 C 找 add 方法
super().add(m) # 第六步
# d.n = 11
self.n += 3
# d.n = 14 class D(B, C):
def __init__(self):
self.n = 5 def add(self, m):
# 第一步
print('self is {0} @DDD.add'.format(self))
# self 的 MRO 是 [D, B, C, A, object]
# 從 D 之後的 [B, C, A, object] 中查詢 add 方法
# 從 B 找 add 方法
super().add(m) # 第七步
# d.n = 14
self.n += 5
# self.n = 19 d = D()
d.add(2)
print(d.n)

先看看 D 類的 MRO

print(D.__mro__)

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

輸出結果

self is <__main__.D object at 0x10c14a190> @DDD.add
self is <__main__.D object at 0x10c14a190> @BBB.add
self is <__main__.D object at 0x10c14a190> @CCC.add
self is <__main__.D object at 0x10c14a190> @AAA.add
19

呼叫順序的確是 D、B、C、A

執行順序

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)

執行順序圖