這裡的範例是可在 Python 3 執行的獨立小程式;輸出框是預先核對的結果。題庫作答時,請保留題目提供的 solve 與參數,使用 return 交回答案。模組章的雙檔範例會另外標明操作方式。
01|類別是一種整理資料與行為的方式
當一份資料有相關的操作時,可以用 class 把它們放在一起。類別描述這種物件應有哪些資料和功能;實際建立出來的一個物件稱為實例。先從一個簡單計數器開始,不需要學繼承或設計模式。
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 則只讀取。呼叫方法要加括號;讀取屬性時不加括號。
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|不同實例保有自己的狀態
使用同一個類別建立兩次,會得到兩份實例。改變第一個的屬性,不應連帶改變第二個。這讓你可以用同一套操作方式管理多個計數器、商品或練習紀錄。
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 先檢查容量,再決定是否新增,並回傳是否成功。狀態更新與回傳值是兩件事,都要讀清楚。
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 建立。用兩個物件測試,比只測一個更容易發現共用狀態錯誤。
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 自訂物件,因為測資不一定知道如何比較它。
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
先不要急著看別人的寫法
- 為 Counter 加上 reset 方法,把 value 設回 0,並測試呼叫前後的值。
- 建立兩個 Bookshelf,只向第一個加入書籍,第二個應有什麼狀態?
- 一題要求最後回傳物品名稱清單,為什麼 return shelf 與 return shelf.books 不相同?
先用紙筆預測,再改動範例中的數字或文字。結果與預期不同時,找出最早開始不同的那一步。
把這幾件事帶走
- 類別描述功能,實例保留各自的資料。
- self 把方法連到目前這個物件。
- 測試多個實例、重複操作、空操作與容量邊界。
FROM READING TO PRACTICE
用三道免費題,練習本章觀念
先做簡單題確認語法,再把幾個步驟組合起來。三題都來自本章主題的新版免費題庫,難度表示需要組合的基本概念。
延伸閱讀:Python 官方文件
以下是本章查證使用的官方資料。可以先完成入門練習,再回來查閱更完整的參數與說明。