CodeSprout程式萌芽

CHAPTER 09 · PYTHON FOUNDATIONS

Class (類別)

把資料與操作整理成物件,用屬性記錄狀態,用方法描述可以做的事。

這一章學會
  • 分辨類別與實例
  • 使用 __init__、self、屬性與方法
  • 建立彼此獨立的物件,透過 solve 回傳基本資料
先預測結果,再看輸出。

這裡的範例是可在 Python 3 執行的獨立小程式;輸出框是預先核對的結果。題庫作答時,請保留題目提供的 solve 與參數,使用 return 交回答案。模組章的雙檔範例會另外標明操作方式。

01|類別是一種整理資料與行為的方式

當一份資料有相關的操作時,可以用 class 把它們放在一起。類別描述這種物件應有哪些資料和功能;實際建立出來的一個物件稱為實例。先從一個簡單計數器開始,不需要學繼承或設計模式。

PYTHON程式碼
class Counter:
    def __init__(self, start):
        self.value = start

counter = Counter(3)
print(counter.value)
預期輸出
3

02|__init__ 與 self 各做什麼

呼叫 Counter(3) 建立實例時,__init__ 會設定這份實例的初始狀態。self 指向目前這個實例,self.value 是屬於它的資料;start 則是這次傳入的參數。__init__ 的名稱前後各有兩個底線,不要少打一個。

  • __init__ 負責初始化,不要回傳數字或其他自訂值。
  • 一般實例方法第一個參數寫 self;呼叫 counter.add(2) 時,Python 會自動傳入該實例。
  • value 是區域名字,self.value 是實例屬性;需要在其他方法繼續使用的狀態,通常存成 self.value。

03|方法是放在類別裡的函式

方法可以讀取或改變目前實例的狀態,也可以回傳結果。下面的 add 修改 value,read 則只讀取。呼叫方法要加括號;讀取屬性時不加括號。

PYTHON程式碼
class Counter:
    def __init__(self, start=0):
        self.value = start

    def add(self, amount):
        self.value += amount

    def read(self):
        return self.value

counter = Counter(2)
counter.add(5)
print(counter.read())
print(counter.value)
預期輸出
7
7

04|不同實例保有自己的狀態

使用同一個類別建立兩次,會得到兩份實例。改變第一個的屬性,不應連帶改變第二個。這讓你可以用同一套操作方式管理多個計數器、商品或練習紀錄。

PYTHON程式碼
class Counter:
    def __init__(self, start):
        self.value = start

    def add(self, amount):
        self.value += amount

first = Counter(1)
second = Counter(10)
first.add(4)
print(first.value)
print(second.value)
預期輸出
5
10

05|把規則放進方法

物件的方法可以維護狀態規則。下例書架最多放固定數量的書,add_book 先檢查容量,再決定是否新增,並回傳是否成功。狀態更新與回傳值是兩件事,都要讀清楚。

PYTHON程式碼
class Bookshelf:
    def __init__(self, capacity):
        self.capacity = capacity
        self.books = []

    def add_book(self, title):
        if len(self.books) >= self.capacity:
            return False
        self.books.append(title)
        return True

shelf = Bookshelf(2)
print(shelf.add_book("Python 入門"))
print(shelf.add_book("故事集"))
print(shelf.add_book("筆記本"))
print(shelf.books)
預期輸出
True
True
False
['Python 入門', '故事集']

06|清單屬性要在 __init__ 建立

若把 books = [] 寫在 class 區塊中,而不是 __init__ 裡,這份清單可能被所有實例共用。每個物件各自擁有的資料,應在初始化時用 self 建立。用兩個物件測試,比只測一個更容易發現共用狀態錯誤。

PYTHON程式碼
class Notebook:
    def __init__(self):
        self.notes = []

    def add(self, text):
        self.notes.append(text)

first = Notebook()
second = Notebook()
first.add("記得測試空清單")
print(first.notes)
print(second.notes)
預期輸出
['記得測試空清單']
[]

07|在 solve 裡建立與使用物件

類別題也從 solve 進入。先根據參數建立物件,依序呼叫方法,最後回傳文字、數字、清單或字典等題目要求的資料。不要直接 return 自訂物件,因為測資不一定知道如何比較它。

PYTHON程式碼
class Counter:
    def __init__(self, start):
        self.value = start

    def add(self, amount):
        self.value += amount

def solve(start, changes):
    counter = Counter(start)
    for amount in changes:
        counter.add(amount)
    return counter.value

print(solve(10, [2, -3, 5]))
print(solve(4, []))
預期輸出
14
4

08|什麼時候需要類別

只是把一個數字加倍,用函式就很清楚;需要保留多個相關屬性、讓多個操作持續改變同一份狀態時,類別會更有幫助。先能說清楚『物件記錄什麼、能做什麼、哪些狀態不合法』,再寫 class,不要為了使用類別把簡單問題變複雜。

  • 屬性描述資料,例如 capacity、books。
  • 方法描述操作,例如 add_book。
  • 建立實例後依序操作,最後檢查狀態與回傳值。

WHEN SOMETHING GOES WRONG

遇到錯誤,先檢查這裡

方法少了 self

一般實例方法定義要接收 self。呼叫 object.method(...) 時不用再手動傳入 object。

AttributeError:屬性沒建立或拼錯

檢查 __init__ 是否確實執行,以及 self.value 的拼字與大小寫是否一致。value 與 values 不同。

不同物件的清單一起變動

檢查是否把可變清單寫成類別屬性,或只是把另一個名字指定為同一實例。每個實例的清單放在 __init__ 中建立。

PAUSE AND TRY

先不要急著看別人的寫法

  1. 為 Counter 加上 reset 方法,把 value 設回 0,並測試呼叫前後的值。
  2. 建立兩個 Bookshelf,只向第一個加入書籍,第二個應有什麼狀態?
  3. 一題要求最後回傳物品名稱清單,為什麼 return shelf 與 return shelf.books 不相同?

先用紙筆預測,再改動範例中的數字或文字。結果與預期不同時,找出最早開始不同的那一步。

把這幾件事帶走

  • 類別描述功能,實例保留各自的資料。
  • self 把方法連到目前這個物件。
  • 測試多個實例、重複操作、空操作與容量邊界。

FROM READING TO PRACTICE

用三道免費題,練習本章觀念

先做簡單題確認語法,再把幾個步驟組合起來。三題都來自本章主題的新版免費題庫,難度表示需要組合的基本概念。

延伸閱讀:Python 官方文件

以下是本章查證使用的官方資料。可以先完成入門練習,再回來查閱更完整的參數與說明。