iT邦幫忙

2024 iThome 鐵人賽

DAY 4
1
自我挑戰組

學習網頁開發系列 第 4

Python super() 到底在幹嘛

  • 分享至 

  • xImage
  •  

Super 的使用特徵

有寫過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 的尋找順續:MRO

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 是如何辦到的

Super 這個不帶任何參數的函式,到底是用了什麼黑魔法,這麼精準的知道在什麼時候要用誰的 MRO 呢?

首先, super() 回傳的並不是親層或是自定義的人和一個物件。實際上執行 super() 時,會先回傳一個 Super class 的 instance,所以實際上 super() 是在進行實體化。

super 是一個 proxy object, 使用這種 object 的目標就是可以在的執行某個方法前先執行其他程式。而 super 這個不需要參數就可以抓到定位的黑魔法,就是在這個時候去攫取相關資訊的。

抓取呼叫 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

實際上,只要我們在 Class 加上 super 這個字,python interpreter 就會自動幫我們在該 class 加上 __class__ 這個屬性。就算我們不呼叫 super,指是列它出來也一樣。

所以,我們可以把 super() 看成 super(class, self)。這樣就比較好理解他是怎麼定位自己的。

後記

我們也可以自己在 class 外呼叫 sup = super(B, b),雖然我目前不知道有什麼用。


上一篇
git 協作流程
下一篇
JWT vs Session
系列文
學習網頁開發13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言