Python 多型 (Polymorphism)

多型是指相同的介面可以有不同的實作方式。在 Python 中,多型讓不同類別的物件可以用相同的方式呼叫方法。

基本概念

多型的核心思想是:不同類別的物件可以有相同名稱的方法,但行為不同。

class Dog:
    def speak(self):
        return "Woof!"

class Cat:
    def speak(self):
        return "Meow!"

class Duck:
    def speak(self):
        return "Quack!"

# 多型:用相同的方式呼叫不同類別的方法
animals = [Dog(), Cat(), Duck()]

for animal in animals:
    print(animal.speak())

輸出:

Woof!
Meow!
Quack!

鴨子型別 (Duck Typing)

Python 採用「鴨子型別」:如果它走起來像鴨子、叫起來像鴨子,那它就是鴨子。

不需要繼承相同的父類別,只要有相同的方法就可以:

class Bird:
    def fly(self):
        print("Bird is flying")

class Airplane:
    def fly(self):
        print("Airplane is flying")

class Fish:
    def swim(self):
        print("Fish is swimming")

def make_it_fly(thing):
    thing.fly()  # 只要有 fly 方法就可以

make_it_fly(Bird())      # Bird is flying
make_it_fly(Airplane())  # Airplane is flying
# make_it_fly(Fish())    # AttributeError: 沒有 fly 方法

透過繼承實現多型

class Shape:
    def area(self):
        raise NotImplementedError("Subclass must implement")
    
    def perimeter(self):
        raise NotImplementedError("Subclass must implement")

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)

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        import math
        return math.pi * self.radius ** 2
    
    def perimeter(self):
        import math
        return 2 * math.pi * self.radius

# 多型使用
def print_shape_info(shape):
    print(f"Area: {shape.area():.2f}")
    print(f"Perimeter: {shape.perimeter():.2f}")

shapes = [Rectangle(5, 3), Circle(4)]

for shape in shapes:
    print_shape_info(shape)
    print()

方法覆寫 (Method Overriding)

子類別可以覆寫父類別的方法,提供不同的實作:

class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "Some sound"
    
    def info(self):
        return f"{self.name} says: {self.speak()}"

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

animals = [Dog("Buddy"), Cat("Whiskers")]

for animal in animals:
    print(animal.info())

輸出:

Buddy says: Woof!
Whiskers says: Meow!

運算子多載 (Operator Overloading)

Python 允許自訂運算子的行為:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)
    
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)
    
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    
    def __str__(self):
        return f"Vector({self.x}, {self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)

print(v1 + v2)  # Vector(4, 6)
print(v2 - v1)  # Vector(2, 2)
print(v1 * 3)   # Vector(3, 6)
print(v1 == Vector(1, 2))  # True

內建函數的多型

Python 的內建函數本身就是多型的:

# len() 對不同型別有不同行為
print(len("Hello"))     # 5(字串長度)
print(len([1, 2, 3]))   # 3(list 長度)
print(len({"a": 1}))    # 1(dict 長度)

# + 運算子對不同型別有不同行為
print(1 + 2)            # 3(數字相加)
print("Hello" + "World") # HelloWorld(字串連接)
print([1, 2] + [3, 4])  # [1, 2, 3, 4](list 連接)

實現自己的多型介面

class PaymentMethod:
    def pay(self, amount):
        raise NotImplementedError

class CreditCard(PaymentMethod):
    def __init__(self, card_number):
        self.card_number = card_number
    
    def pay(self, amount):
        print(f"Paying ${amount} with credit card {self.card_number[-4:]}")
        return True

class PayPal(PaymentMethod):
    def __init__(self, email):
        self.email = email
    
    def pay(self, amount):
        print(f"Paying ${amount} via PayPal ({self.email})")
        return True

class Bitcoin(PaymentMethod):
    def __init__(self, wallet):
        self.wallet = wallet
    
    def pay(self, amount):
        print(f"Paying ${amount} in Bitcoin to {self.wallet[:8]}...")
        return True

# 多型使用
def process_payment(payment_method, amount):
    if payment_method.pay(amount):
        print("Payment successful!")
    else:
        print("Payment failed!")

# 不同的支付方式,相同的介面
payments = [
    CreditCard("1234-5678-9012-3456"),
    PayPal("user@example.com"),
    Bitcoin("1A2B3C4D5E6F7G8H")
]

for payment in payments:
    process_payment(payment, 100)
    print()

協定 (Protocol) - Python 3.8+

使用 typing.Protocol 定義結構化子型別:

from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None:
        ...

class Circle:
    def draw(self) -> None:
        print("Drawing a circle")

class Square:
    def draw(self) -> None:
        print("Drawing a square")

def render(shape: Drawable) -> None:
    shape.draw()

# Circle 和 Square 不需要繼承 Drawable
# 只要有 draw 方法就符合協定
render(Circle())  # Drawing a circle
render(Square())  # Drawing a square

多型的好處

  1. 程式碼重用:可以撰寫通用的程式碼處理不同類型的物件
  2. 擴展性:新增新類別時不需要修改現有程式碼
  3. 靈活性:可以在執行時決定使用哪個實作
  4. 可讀性:統一的介面讓程式碼更容易理解