Python 繼承 (Inheritance)

繼承讓子類別可以繼承父類別的屬性和方法,實現程式碼重用和擴展。

基本繼承

# 父類別(基類別)
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        print(f"{self.name} makes a sound")

# 子類別(衍生類別)
class Dog(Animal):
    def bark(self):
        print(f"{self.name} says: Woof!")

# 使用
dog = Dog("Buddy")
dog.speak()  # Buddy makes a sound(繼承自 Animal)
dog.bark()   # Buddy says: Woof!(Dog 自己的方法)

方法覆寫 (Override)

子類別可以覆寫父類別的方法:

class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        print(f"{self.name} makes a sound")

class Dog(Animal):
    def speak(self):
        print(f"{self.name} says: Woof!")

class Cat(Animal):
    def speak(self):
        print(f"{self.name} says: Meow!")

dog = Dog("Buddy")
cat = Cat("Whiskers")

dog.speak()  # Buddy says: Woof!
cat.speak()  # Whiskers says: Meow!

super() 函數

使用 super() 呼叫父類別的方法:

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def info(self):
        return f"{self.name}, {self.age} years old"

class Dog(Animal):
    def __init__(self, name, age, breed):
        super().__init__(name, age)  # 呼叫父類別的 __init__
        self.breed = breed
    
    def info(self):
        base_info = super().info()  # 呼叫父類別的 info
        return f"{base_info}, breed: {self.breed}"

dog = Dog("Buddy", 3, "Labrador")
print(dog.info())  # Buddy, 3 years old, breed: Labrador

isinstance() 和 issubclass()

class Animal:
    pass

class Dog(Animal):
    pass

dog = Dog()

# isinstance 檢查物件是否為某類別的實例
print(isinstance(dog, Dog))     # True
print(isinstance(dog, Animal))  # True(Dog 繼承自 Animal)
print(isinstance(dog, object))  # True(所有類別都繼承自 object)

# issubclass 檢查類別是否為另一類別的子類別
print(issubclass(Dog, Animal))  # True
print(issubclass(Dog, object))  # True
print(issubclass(Animal, Dog))  # False

多重繼承

Python 支援多重繼承,一個類別可以繼承多個父類別:

class Flyable:
    def fly(self):
        print("Flying...")

class Swimmable:
    def swim(self):
        print("Swimming...")

class Duck(Flyable, Swimmable):
    def quack(self):
        print("Quack!")

duck = Duck()
duck.fly()   # Flying...
duck.swim()  # Swimming...
duck.quack() # Quack!

方法解析順序 (MRO)

當一個類別繼承自多個父類別時,Python 需要一個規則來決定當呼叫某個方法時,應該優先使用哪一個父類別的版本。這個規則稱為 方法解析順序 (Method Resolution Order, MRO)

Python 使用 C3 線性化演算法 (C3 Linearization) 來計算 MRO。簡單來說,它的搜尋順序原則如下:

  1. 先搜尋自己 (子類別)。
  2. 依照繼承列表由左至右搜尋父類別。
  3. 如果父類別還有父類別,則深度優先,但會保持由左至右的順序,且確保每個類別只被搜尋一次(對於鑽石繼承結構特別重要)。

我們可以使用 類別名稱.mro() 方法或是 類別名稱.__mro__ 屬性來查看實際的搜尋順序。

class A:
    def method(self):
        print("A")

class B(A):
    def method(self):
        print("B")

class C(A):
    def method(self):
        print("C")

class D(B, C):
    pass

d = D()
d.method()  # B(根據 MRO)

# 查看 MRO
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

使用 super() 處理多重繼承

在單一繼承中,super() 通常指的就是「父類別」。但在多重繼承中,super() 的行為更加強大且重要:它指的不是「父類別」,而是 MRO 順序中的下一個類別

確實使用 super() 對於多重繼承非常關鍵,因為它能確保:

  1. MRO 鏈條的完整執行:每個類別的方法只會被執行一次,避免重複呼叫(例如鑽石繼承問題)。
  2. 合作式多重繼承 (Cooperative Multiple Inheritance):只要每個類別都正確呼叫 super(),控制權就會沿著 MRO 鏈條傳遞下去,直到最頂層的 object 類別。

讓我們看一個範例,觀察 super() 如何沿著 MRO 傳遞呼叫:

class A:
    def __init__(self):
        print("A init")
        super().__init__()

class B(A):
    def __init__(self):
        print("B init")
        super().__init__()

class C(A):
    def __init__(self):
        print("C init")
        super().__init__()

class D(B, C):
    def __init__(self):
        print("D init")
        super().__init__()

d = D()

輸出:

D init
B init
C init
A init

Mixin (混入模式)

Mixin 是一種特殊的設計模式,它是一個包含了特定功能方法的類別,但它的設計目的不是用來獨立實例化 (instantiated),而是用來混入 (mix in) 到其他子類別中,以提供額外的功能。

Mixin 的特點:

  1. 單一職責:通常只負責一組特定的功能(例如:將物件轉為 JSON、記錄 Log、權限驗證)。
  2. 不獨立使用:不應該單獨建立 Mixin 的實例。
  3. 非階層關係:它代表的是 "can-do"(能做什麼)的特徵,而不是傳統繼承的 "is-a"(是什麼)的關係。

使用 Mixin 可以讓我們透過組合的方式為類別添加功能,避免過度複雜的繼承階層。

class JsonMixin:
    def to_json(self):
        import json
        return json.dumps(self.__dict__)

class LogMixin:
    def log(self, message):
        print(f"[{self.__class__.__name__}] {message}")

class User(JsonMixin, LogMixin):
    def __init__(self, name, email):
        self.name = name
        self.email = email

user = User("Alice", "alice@example.com")
print(user.to_json())  # {"name": "Alice", "email": "alice@example.com"}
user.log("User created")  # [User] User created

抽象類別 (Abstract Base Classes)

抽象類別 (ABC) 是一種不能被實例化的類別,它的主要用途是定義介面 (Interface)規範子類別必須實作的方法。這有點像是定義一份「合約」,任何繼承此抽象類別的子類別,都必須遵守這份合約(實作指定的方法),否則該子類別也不能被實例化。

在 Python 中,我們使用 abc 模組來實現抽象類別:

  1. 類別必須繼承 abc.ABC
  2. 使用 @abc.abstractmethod 裝飾器來標記抽象方法。

這在大型專案開發時非常有用,能確保不同的子類別都擁有一致的 API 介面。

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
    
    @abstractmethod
    def perimeter(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height
    
    def perimeter(self):
        return 2 * (self.width + self.height)

# shape = Shape()  # TypeError: 不能實例化抽象類別

rect = Rectangle(5, 3)
print(rect.area())       # 15
print(rect.perimeter())  # 16

實際範例

from abc import ABC, abstractmethod

class Employee(ABC):
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
    
    @abstractmethod
    def calculate_bonus(self):
        pass
    
    def __str__(self):
        return f"{self.name} - ${self.salary}"

class Manager(Employee):
    def __init__(self, name, salary, team_size):
        super().__init__(name, salary)
        self.team_size = team_size
    
    def calculate_bonus(self):
        return self.salary * 0.2 + self.team_size * 100

class Developer(Employee):
    def __init__(self, name, salary, programming_languages):
        super().__init__(name, salary)
        self.programming_languages = programming_languages
    
    def calculate_bonus(self):
        return self.salary * 0.1 + len(self.programming_languages) * 50

# 使用
manager = Manager("Alice", 80000, 5)
developer = Developer("Bob", 70000, ["Python", "JavaScript", "Go"])

print(f"{manager.name}'s bonus: ${manager.calculate_bonus()}")
# Alice's bonus: $16500.0

print(f"{developer.name}'s bonus: ${developer.calculate_bonus()}")
# Bob's bonus: $7150.0