Featured image of post Python OOP入門指南 (二) :抽象  掌握 ABC 與 @abstractmethod

Python OOP入門指南 (二) :抽象 掌握 ABC 與 @abstractmethod

本篇教你從零開始理解什麼是 Python 的抽象類別(abstract class),用簡單範例幫助新手學會設計規範與繼承。


抽象類別是什麼?

抽象類別(Abstract Class) 是一種 無法被直接實例化 的類別,它的主要用途是作為「模板」提供一套明確的設計規範,讓子類別去實作必要的方法。

簡單來說,抽象就是隱藏不必要的細節,保留對使用者有意義的部分。

想像你在便利商店點飲料,你只會按下「冰美式」,不會去控制水溫、豆量、壓力值。咖啡機幫你包辦了背後的複雜程序,而你只需要操作「make_drink()」這個介面。

抽象讓你只看見「你需要的操作」,不被內部細節干擾。

抽象類別的三大特性:

  • 不能直接建立實例
  • 包含一個或多個抽象方法
  • 子類別必須實作所有抽象方法才能使用

抽象類別像是一份“介面契約”,強迫所有子類別遵守這份規定,避免遺漏必要功能。

為什麼要使用抽象類別?

優點 說明
統一規範 強制子類別都實作特定方法,確保一致性
易於擴充 子類別的功能結構清晰,不易出錯
避免遺漏 每位開發者知道該實作哪些方法
適合團隊合作 明確開發規範,合作更順暢

Python 如何實作抽象類別?

Python 並不像 Java、C++ 內建 abstract 關鍵字,而是透過內建模組 abc(Abstract Base Class)來達成。

建立抽象類別的三步驟 :

第一步:匯入模組

1
2
import abc 
from abc import ABC, abstractmethod  # ABC 是內建的抽象類別基底類別

可不要覺得 ABC 是因為人家懶得想名字而隨便取的唷
只是簡寫真的剛好是ABC~

abc = 模組名稱
ABC = Abstract Base Classes (模組基底類別)
@abstractmethod = 用來定義抽象方法的裝飾器

第二步:定義抽象類別與抽象方法

1
2
3
4
class Animal(ABC):  # 繼承 ABC 成為抽象類別
    @abstractmethod
    def make_sound(self):  # 抽象方法,沒有實作內容
        pass

第三步:由子類別實作抽象方法

1
2
3
4
5
6
7
class Dog(Animal):
    def make_sound(self):  # 必須實作父類別的抽象方法
        print("汪汪!")

class Cat(Animal):
    def make_sound(self):
        print("喵喵!")

一旦類別中包含 @abstractmethod 方法,該類別就不能被實例化
它會標記一個方法為「抽象」,代表這個方法在父類別中不提供實作細節,而是留給子類別自行定義。

如果子類別沒有實作所有抽象方法,將無法建立該類別的實例,會拋出 TypeError。(看下方範例)

1
2
3
4
5
6
class Dog(Animal):
    def make_sound(self):
        print("汪汪")  # 有實作 ✔️

d = Dog()  # 建立實例
d.make_sound()  # 汪汪
1
2
3
4
5
class Dog(Animal):
    def make_sound(self):
        pass  # ❌ 雖然語法正確,但沒有實際內容,視為未實作

# d = Dog()  ❌ TypeError: Can't instantiate abstract class Dog with abstract method make_sound

✅ 完整範例:動物叫聲系統

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from abc import ABC, abstractmethod

# 抽象類別
class Animal(ABC):
    @abstractmethod
    def make_sound(self):
        pass

    def sleep(self):
        print("Zzz...")  # 抽象類別也可有一般方法

# 子類別實作
class Dog(Animal):
    def make_sound(self):
        print("汪汪!")

class Cat(Animal):
    def make_sound(self):
        print("喵喵!")

# a = Animal()  # ❌ TypeError:無法建立抽象類別的實例

d = Dog()
d.make_sound()  # 汪汪!
d.sleep()       # Zzz...

💬 常見問題 Q&A

Q1: 抽象和封裝有什麼不同?

抽象是隱藏實作行為、只對外提供接口;封裝是保護資料不被任意存取。

Q2:一定要用 ABC 嗎?

是的,繼承 ABC 或使用 metaclass = ABCMeta 是 Python 抽象類別的必要設定。

1
2
3
4
5
# 另一種寫法
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def make_sound(self):
        pass

Q3:如果子類別沒有實作抽象方法會怎樣?

會在建立子類別實例時拋出 TypeError,提醒你沒有實作必要的方法。
(↑看上面動物叫聲系統範例)

Q4:抽象類別可以包含普通方法嗎?

可以。除了抽象方法,也可以寫一般方法,提供子類別共用的功能。

1
2
3
4
5
6
7
class Animal(ABC):
    @abstractmethod
    def make_sound(self):    # 抽象方法
        pass    # 沒有實作內容,只是一個規範接口

    def sleep(self):         # 一般方法
        print("Zzz...")

Q5:抽象類別可以加屬性嗎?

可以,但建議放共用或初始化用的屬性,具體邏輯留給子類別處理。

Q6:什麼時候應該使用抽象類別?

當你希望每個子類別都遵守統一規則或結構(如:付款、載具、通知類別)。

Q7:ABC 是什麼?和 metaclass=ABCMeta 有什麼關係?

ABC 就是 metaclass=ABCMeta 的語法糖(syntactic sugar)。 寫 class A(ABC): 的效果等同於 class A(metaclass=ABCMeta)。 這樣寫更簡潔可讀,也更常見


💡 延伸補充:metaclass 是什麼?

在 Python 中,一切皆物件,類別也是一種物件,它的類型就是 type

1
2
3
4
class Product:
    pass

print(type(Product))  # <class 'type'>

也就是說:

  1. Product 是一個類別(class)

  2. 但它本身也是一個物件

  3. 它是由 type 類別創造出來的物件(也就是 Python 中預設的 metaclass)

1
2
3
4
5
6
from abc import ABC, abstractmethod

class Animal(ABC):  # 背後其實是 metaclass=ABCMeta
    @abstractmethod
    def make_sound(self):
        pass

當你使用 metaclass=ABCMeta,就是告訴 Python 使用一個特別的類型(MetaClass)來定義這個類別,使其具備「抽象類別」的行為。


總結:建立抽象類別 3 步驟

1️⃣ from abc import ABC, abstractmethod
2️⃣ class 類別名(ABC):
3️⃣ 用 @abstractmethod

抽象類別是一種讓程式設計更有「架構」的利器,特別適合用在大型專案或多人開發時統一規格。


🤔 腦力激盪 - 支付模組設計

假設你正在寫一個支付模組(Payment),有哪些方法應該是抽象的?你會怎麼實作 CreditCardLinePay街口支付 等子類別?


(參考答案)

(1) 我們可以先定義一個抽象基底類別 Payment,所有支付方式都要遵守以下統一接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from abc import ABC, abstractmethod

class Payment(ABC):
    @abstractmethod
    def authorize(self, amount):
        """驗證是否可以付款,例如檢查餘額或授權碼"""
        pass

    @abstractmethod
    def pay(self, amount):
        """執行實際付款邏輯"""
        pass

    @abstractmethod
    def refund(self, amount):
        """退款流程"""
        pass

(2) 子類別實作 (CreditCard 信用卡支付 、 LinePay 、 街口支付)

1
2
3
4
5
6
7
8
9
class CreditCard(Payment):
    def authorize(self, amount):
        print(f"信用卡授權 {amount} 元通過")

    def pay(self, amount):
        print(f"已用信用卡付款 {amount} 元")

    def refund(self, amount):
        print(f"已退還信用卡款項 {amount} 元")
1
2
3
4
5
6
7
8
9
class LinePay(Payment):
    def authorize(self, amount):
        print(f"LinePay 授權成功,金額:{amount}")

    def pay(self, amount):
        print(f"LinePay 完成付款 {amount} 元")

    def refund(self, amount):
        print(f"LinePay 退款 {amount} 元成功")
1
2
3
4
5
6
7
8
9
class JKOPay(Payment):
    def authorize(self, amount):
        print(f"街口支付已授權 {amount} 元")

    def pay(self, amount):
        print(f"街口支付完成付款 {amount} 元")

    def refund(self, amount):
        print(f"街口支付已完成退款 {amount} 元")
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def process_payment(method: Payment, amount: int):
    method.authorize(amount)
    method.pay(amount)
    method.refund(amount)

process_payment(CreditCard(), 500)
process_payment(LinePay(), 800)


# 信用卡授權 500 元通過
# 已用信用卡付款 500 元
# 已退還信用卡款項 500 元

# LinePay 授權成功,金額:800
# LinePay 完成付款 800 元
# LinePay 退款 800 元成功

以上實作包含了OOP中抽象多型的應用:

  • 抽象(Abstraction) 是為了建立統一的介面,像 authorize()、pay()、refund()。

  • 多型(Polymorphism) 則是利用這個介面,讓不同子類(如 CreditCard, LinePay)在 不改變函式 process_payment() 寫法的前提下實現不同的行為。

下個章節會帶大家更了解OOP的多型! 請拭目以待~


comments powered by Disqus
2025 Michelle's Blog. All rights reserved
Built with Hugo
Theme Stack designed by Jimmy