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
多型的好處
- 程式碼重用:可以撰寫通用的程式碼處理不同類型的物件
- 擴展性:新增新類別時不需要修改現有程式碼
- 靈活性:可以在執行時決定使用哪個實作
- 可讀性:統一的介面讓程式碼更容易理解