有寫過python的或多或少都有看過 super 函式。常見於 class 繼承時,子類別呼叫上層類別屬性或函式的方法。
範例:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal):
def __init__(self, name, breed):
# Call the __init__ method of the parent class (Animal)
super().__init__(name)
self.breed = breed
def speak(self):
# Extend the functionality of the parent's speak method
return f"{super().speak()} and barks"
dog = Dog("Rex", "Labrador")
print(dog.speak()) # Output: Rex makes a sound and barks
當然, super 也可用於 classmethod:
class Animal:
species = "Generic Animal"
@classmethod
def describe(cls):
return f"This is a {cls.species}"
class Dog(Animal):
species = "Dog"
@classmethod
def describe(cls):
# Call the describe method of the parent class
description = super().describe()
return f"{description} that is a loyal companion"
print(Animal.describe()) # Output: This is a Generic Animal
print(Dog.describe()) # Output: This is a Dog that is a loyal companion
假如中間沒有相應函式,super 就會繼續找下去:
class Animal:
def speak(self):
return 'hello world'
class Dog(Animal):
pass
class Yorkshire(Dog):
def speak(self):
super().speak
y = Yorkshire()
print(y.speak()) # hello world
到目前為止的 super 的行為都很符合想像,但要是是多重繼承呢?
class Animal:
def speak(self):
print('this is an animal')
class Dog(Animal):
def speak(self):
print("this is a Dog")
super().speak
class Cat(Animal):
def speak(self):
print("this is a Cat")
super().speak
class Yorkshire(Dog, Cat):
def speak(self):
print("this is a Yorkshire")
super().speak
y = Yorkshire()
y.speak()
# Output:
# this is a Yorkshire
# this is a Dog
# this is a Cat
# this is an animal
這裡就很有趣了, print 順序居然是 Yorkshire -> Dog -> Cat -> Animal。首先 ,Yorkshire 的 super 呼叫他繼承的第一個親層,這還算好理解。但 Dog 的 super 居然呼叫到了 Cat ? 他們之間又沒有繼承關係,到底怎麼回事?
所以 super 是怎麼決定他的尋找順序的?它又是怎麼找到順序中的類別的呢?
Super 被呼叫時,他會去尋找該 Class 或 Instance 的 MRO,並以此為順位藍圖。
MRO是Python Class 中有一個很重要的概念,全稱 Method Resolution Order,中文翻作方法解析順序。要注意的是,不只方法解析要參照MRO,所有 Attribute 都要。
舉例:
class A:
def hi(self):
print('A')
class B(A):
def hi(self):
print('B')
super().hi()
class C(A):
def hi(self):
print('C')
super().hi()
class D(B,C)
def hi(self):
print('D')
super().hi()
print([cls.__name__ for cls in D.__mro__])
# Output:
# [D, C, B, A, object]
# object 是所有 class 的源頭
所以很嚴格來說,參照 MRO 的 Super 並不是找尋 親層
,而是找 MRO上的下一位。
以上述例子為例,呼叫D時,我們知道順序會是 D->C->B->A。但假設我們直接呼叫C呢?這時候順序會是 C->A 而不是 C->B->A,因為從C開始的話,我們就要以 C 的 MRO 為主。而 C 的 MRO 是沒有 B 的。
Super 這個不帶任何參數的函式,到底是用了什麼黑魔法,這麼精準的知道在什麼時候要用誰的 MRO 呢?
首先, super() 回傳的並不是親層或是自定義的人和一個物件。實際上執行 super() 時,會先回傳一個 Super class 的 instance,所以實際上 super() 是在進行實體化。
super 是一個 proxy object, 使用這種 object 的目標就是可以在的執行某個方法前先執行其他程式。而 super 這個不需要參數就可以抓到定位的黑魔法,就是在這個時候去攫取相關資訊的。
在 python 中,我們其實可以直接找目前正在執行的 stack frame。
def current_callers():
frame = inspect.currentframe()
caller_locals = frame.f_back.f_locals
print(caller_locals)
def test():
a = 5
b = "aloha"
current_callers()
test()
# Output:
# {'a': 5, b: 'aloha'}
# 或是
class Example:
def test(self):
current_callers()
e = Example()
e.test()
# Output:
# { 'self': Example object e at some address }
所以 super 透過這個方法,可以很輕易的知道最一開始是哪個物件呼叫了他。
但就算如此, super 又是怎麼知道它目前這段程式是屬於哪個 class 的呢?
實際上,只要我們在 Class 加上 super 這個字,python interpreter 就會自動幫我們在該 class 加上 __class__
這個屬性。就算我們不呼叫 super,指是列它出來也一樣。
所以,我們可以把 super() 看成 super(class, self)。這樣就比較好理解他是怎麼定位自己的。
我們也可以自己在 class 外呼叫 sup = super(B, b),雖然我目前不知道有什麼用。