跳到主要內容

Day 14 物件導向進階與小專案

Day 14 物件導向進階與小專案

引言

昨天學會了類別、屬性、方法與特殊方法,已經能寫出具備基本結構的物件。今天要把物件導向的三大支柱補完:繼承讓我們在既有類別上延伸新功能、封裝把內部細節藏起來保護資料、多型則讓不同物件對同一訊息有不同反應。學完這些,就能寫出真正可重複使用、容易維護的類別設計。這三個觀念不只在 Python 中重要,幾乎所有主流的物件導向語言(Java、C++、C#、Kotlin)都把它們列為核心。

為了讓觀念落地,我們會在最後用這些機制實作一個簡單的記帳系統:包含兩種類型的交易(收入與支出)、能計算總額、也能列出明細。這個小專案同時會用到檔案讀寫,把前面學的內容串起來。專案規模雖然小,卻已經用到了類別階層、方法覆寫、多型與檔案 I/O,是個很棒的綜合練習。完成後你會發現,這個記帳系統其實已經具備了一個真實 App 的核心架構:資料模型(類別)+ 商業邏輯(方法)+ 儲存層(JSON)。

繼承:讓子類別複用父類別

繼承(inheritance)讓新類別複用既有類別的屬性與方法。被繼承的稱為父類別(base class),衍生的新類別稱為子類別(derived class)。子類別除了擁有父類別的功能,還能新增自己的功能,或覆寫(override)父類別的方法。這樣做的好處是「共同的部分寫一次就好」,差異的部分交給各子類別自己實作。

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError("子類別必須實作 speak()")

class Dog(Animal):
    def speak(self):
        return f"{self.name} 汪汪叫"

class Cat(Animal):
    def speak(self):
        return f"{self.name} 喵喵叫"

animals = [Dog("小黑"), Cat("小花"), Dog("旺財")]
for a in animals:
    print(a.speak())
# 輸出:
# 小黑 汪汪叫
# 小花 喵喵叫
# 旺財 汪汪叫

如果想在子類別呼叫父類別的方法,可以用 super() 取得父類別的實作,這在建構子中特別常用,可以避免重複撰寫屬性初始化的程式碼。

class Employee:
    def __init__(self, name, base_salary):
        self.name = name
        self.base_salary = base_salary

    def monthly_pay(self):
        return self.base_salary

class Manager(Employee):
    def __init__(self, name, base_salary, bonus):
        super().__init__(name, base_salary)
        self.bonus = bonus

    def monthly_pay(self):
        return self.base_salary + self.bonus

m = Manager("小華", 50000, 15000)
print(m.monthly_pay())  # 輸出:65000

Python 支援多重繼承(同時繼承多個父類別),但實務上很容易造成維護困難,建議盡量保持單一繼承,並用組合(在一個類別裡放另一個類別的物件)來解決複用問題。NotImplementedError 是告訴子類別「你必須自己實作這個方法」,這是 Python 慣用的抽象方法約定,等於是介面的溫和版。從 Python 3.4 起,也可以用標準庫的 abc 模組更正式地定義「抽象基底類別」(ABCs),強制子類別實作特定方法,違規時直接拋出錯誤,這在大型框架中很常見。

封裝:用底線保護內部狀態

封裝(encapsulation)的精神是「把實作細節藏起來,只暴露必要的介面」。在 Python 中並沒有真正的 private,但有幾個命名慣例:單底線 _var 表示「內部使用、不建議外部存取」;雙底線 __var 會觸發名稱改寫(name mangling),讓外部更不容易存取到。封裝不是要禁止外部碰,而是要清楚表達「這是內部細節,未來可能改變」。

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.__balance = balance  # 雙底線:不易從外部直接改

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("存款金額必須大於 0")
        self.__balance += amount

    def withdraw(self, amount):
        if amount > self.__balance:
            raise ValueError("餘額不足")
        self.__balance -= amount

    def get_balance(self):
        return self.__balance

acc = BankAccount("小明", 1000)
acc.deposit(500)
print(acc.get_balance())  # 輸出:1500
# print(acc.__balance)    # AttributeError
print(acc._BankAccount__balance)  # 不建議這樣用

實務上,雙底線很少用,多數情況下單底線就足以表達「這是內部變數」的訊號。更重要的是搭配「property」裝飾器,把讀取與寫入屬性的方式寫得像存取屬性一樣自然,但內部可以加上檢查邏輯。設計良好的封裝能讓之後修改內部實作時不必更動外部呼叫,是大型專案能穩定演進的基礎。

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("溫度低於絕對零度")
        self._celsius = value

t = Temperature(25)
print(t.celsius)      # 輸出:25
t.celsius = 30        # 透過 setter 檢查
print(t.celsius)      # 輸出:30

property 讓我們用「看起來像屬性」的語法呼叫,實際上背後還是呼叫函式,因此能加上檢查、轉換、紀錄等邏輯。這種寫法在很多 Python 套件(例如 Django 的 models、PyTorch 的 nn.Module 參數)都很常見,是寫 API 友善類別的關鍵技巧。property 也常和唯讀的「唯讀屬性」搭配使用:只寫 getter 不寫 setter,就能表達「這個值建立後不能改」。

多型:同一個介面、不同行為

多型(polymorphism)讓不同類別的物件,對同一個方法呼叫產生不同行為。第一個範例中的 Dog.speak() 與 Cat.speak() 就是多型:我們呼叫同一個 .speak(),卻得到不同的結果。這也是為什麼我們能用 for 迴圈走訪多種動物,卻不需要在意具體型別。

class Shape:
    def area(self):
        raise NotImplementedError

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w = w
        self.h = h
    def area(self):
        return self.w * self.h

class Circle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        return 3.14159 * self.r ** 2

shapes = [Rectangle(3, 4), Circle(2), Rectangle(5, 5)]
total = sum(s.area() for s in shapes)
print(total)  # 輸出:12 + 12.56636 + 25 = 49.56636

多型讓我們寫「針對抽象介面」的程式,而不是針對具體類別。這正是 PyTorch 設計 nn.Module 的核心觀念:每個模型層都有 forward(),但具體實作各自不同。我們之後寫自訂模型層時,會大量用到這個觀念。多型也是日後理解「為什麼 Python 函式庫這麼靈活」的關鍵,因為呼叫端只需要知道介面,不用知道實作。Python 的內建機制例如 +、len()、for 迴圈也都靠多型:只要物件定義了對應的特殊方法,就能直接套用內建語法,這也是鴨子型別的具體實踐。

小專案:記帳程式

把今天學的內容用一個小專案串起來。我們設計一個記帳系統:所有交易共用一個父類別 Transaction,再分成 Income 與 Expense 兩個子類別。Wallet 負責管理交易清單與計算餘額,並能把資料儲存成 JSON 檔案。為了避免每筆交易都用 dict 處理,採用類別設計能讓加總、儲存的邏輯更清楚。

import json
from datetime import date

class Transaction:
    def __init__(self, amount, note=""):
        self.date = date.today().isoformat()
        self.amount = amount
        self.note = note

    def sign(self):
        raise NotImplementedError

class Income(Transaction):
    def sign(self):
        return +abs(self.amount)

class Expense(Transaction):
    def sign(self):
        return -abs(self.amount)

class Wallet:
    def __init__(self, owner):
        self.owner = owner
        self.items = []

    def add(self, tx):
        self.items.append(tx)

    def balance(self):
        return sum(tx.sign() for tx in self.items)

    def dump(self, path):
        with open(path, "w", encoding="utf-8") as f:
            data = [vars(tx) | {"type": type(tx).__name__} for tx in self.items]
            json.dump(data, f, ensure_ascii=False, indent=2)

w = Wallet("小明的錢包")
w.add(Income(50000, "十月薪水"))
w.add(Expense(1200, "午餐"))
w.add(Expense(800, "交通"))
print(w.balance())  # 輸出:48000
w.dump("wallet.json")

執行後,wallet.json 會儲存三筆交易資料,餘額為 48000。Income 與 Expense 透過覆寫 sign() 達到多型效果:對 Wallet 來說,它只關心每筆交易「加多少或減多少」,具體是收入還是支出則由子類別決定。這就是一個小而完整的物件導向設計。可以再延伸成「每筆交易儲存類別」、「新增篩選本月支出的方法」、「把記錄匯入到 Wallet」,都是不錯的練習。如果想再升級,可以把這個類別放進 my_wallet.py 模組,再用 __main__ 區塊做互動式選單,整個專案就會接近一個可以日常使用的小工具了。

結語

今天把物件導向的繼承、封裝、多型都跑了一遍,並用記帳小專案把觀念落地。這個專案雖然只有 60 行程式碼,卻用到了類別階層、方法覆寫、多型與檔案 I/O,是個很紮實的綜合練習。從明天起,我們會進入資料科學的世界,先用 NumPy 處理陣列運算,再進入 Pandas 做資料清理與分析。這兩套工具的設計哲學都深受物件導向影響,看完今天的內容再去學 NumPy 與 Pandas,會更能體會它們的 API 為什麼長成那樣。如果你對物件導向還不熟悉,建議在進入資料章節前,先自己改造一次這個記帳程式,會比純讀程式碼更有感。

明天,我們會正式踏入 NumPy,認識 ndarray、shape、dtype 與基本運算,這是後續 Pandas 與 PyTorch 張量的共同基礎。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...