訂單是一條小記錄:編號、明細、總價、是否付款。手寫 __init__、__repr__、__eq__ 就是整整一頁樣板。@dataclass 免費生成了什麼?
dataclass 是帶有自動生成方法的普通類,初始化、顯示和相等比較都不必手寫。它適合點、使用者、設定值這類小型記錄。欄位通過註解聲明,預設值則像普通賦值一樣寫在後面。 你還會用 default_factory、在 __post_init__ 中驗證,並用 replace 更新凍結記錄。
用註解聲明欄位
@dataclass 裝飾器讀取帶註解的類屬性,並自動生成 __init__。建立 Point(2, 3) 會按順序給 x 和 y 賦值。自動生成的 __repr__ 讓物件在控制臺裡更易讀。
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
point = Point(2, 3)
print(point)一個小型記錄
添加預設值
帶預設值的欄位可以在構造時省略。沒有預設值的欄位必須放在類體前面。repr 會同時顯示欄位名和目前值。
from dataclasses import dataclass
@dataclass
class User:
name: str
active: bool = True
print(User("Ada"))欄位列表中的預設值
比較與轉換
dataclass 會根據欄位生成 __eq__,所以資料相同的兩個物件相等。asdict 回傳普通字典,便於 JSON 或測試斷言。frozen=True 會禁止之後再給屬性賦值。
from dataclasses import dataclass, asdict
@dataclass(frozen=True)
class Item:
name: str
price: float
item = Item("tea", 3.5)
print(asdict(item))
print(Item("tea", 3.5) == item)相等比較與普通映射
可變預設值需要工廠
dataclass 裡的列表預設值會被所有實例共用,所以 Python 會直接拒絕。field(default_factory=list) 會為每個物件建立新列表。字典、集合和其他可變值同理。
from dataclasses import dataclass, field
@dataclass
class Order:
drink: str
tags: list[str] = field(default_factory=list)
first = Order("latte")
second = Order("tea")
first.tags.append("gift")
print(first.tags)
print(second.tags)
print(first.tags is second.tags)每個實例都有自己的新列表
__post_init__ 驗證並派生
__post_init__ 在自動生成的 __init__ 之後執行。可以用它拒絕不可能的值,並計算必須保持一致的欄位。它應該驗證業務規則,而不是試圖強制執行 Python 根本沒檢查過的型別提示。
from dataclasses import dataclass
@dataclass
class Order:
price: float
quantity: int
total: float = 0.0
def __post_init__(self):
if self.price < 0 or self.quantity <= 0:
raise ValueError("invalid order")
self.total = round(self.price * self.quantity, 2)
for values in [(4.5, 2), (-1, 1)]:
try:
print(Order(*values))
except ValueError as error:
print(type(error).__name__, error)先驗證,再派生總價
用 replace 更新凍結記錄
frozen=True 禁止屬性賦值,讓記錄可以安全共用。replace() 回傳一個新實例,只改動指定欄位,原物件保持不變。這個模式適合撤銷、歷史記錄和設定快照。
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Order:
drink: str
price: float
original = Order("latte", 4.5)
updated = replace(original, price=5.0)
print(original)
print(updated)
print(original == updated)複製後修改,而不是原地改
本課要點
- @dataclass 會生成 __init__、__repr__ 和 __eq__。
- 欄位是帶註解的類屬性。
- 帶預設值的欄位必須放在沒有預設值的欄位之後。
- asdict 會把 dataclass 實例轉換成字典。
- frozen=True 會讓實例不可變。
- 可變欄位用 default_factory;__post_init__ 負責驗證;凍結記錄用 replace 更新。
常見錯誤
- 把沒有預設值的欄位放在有預設值的欄位之後。
- 以為 dataclass 欄位會在執行時驗證型別。
- 試圖給 frozen 實例賦值。
- 寫 tags: list[str] = [],讓所有實例共用同一個列表。
建立一個帶 name 和 price 的 frozen Item dataclass,再列印 asdict(Item('tea', 3.5))。
動手試試
改改變數、先預測輸出,再執行程式碼。第一次點擊「執行程式碼」時會下載 Python 執行時,可能需要幾秒鐘。
練習本課
12 道題,作答後立即看到解釋。