Python Data Classes
在 Python 3.7 中引入的 dataclasses 模組,提供了一個裝飾器 @dataclass,極大地簡化了主要用於儲存資料的類別定義。
為什麼需要 Data Classes?
在傳統類別中,定義一個簡單的資料容器需要寫很多重複的程式碼:
# 傳統寫法
class User:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def __repr__(self):
return f"User(name='{self.name}', age={self.age}, email='{self.email}')"
def __eq__(self, other):
if not isinstance(other, User):
return NotImplemented
return (self.name, self.age, self.email) == (other.name, other.age, other.email)
使用 dataclass 可以自動產生 __init__、__repr__、__eq__ 等方法:
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str
# 自動產生 __init__
u1 = User("Alice", 25, "alice@example.com")
u2 = User("Alice", 25, "alice@example.com")
# 自動產生 __repr__
print(u1) # User(name='Alice', age=25, email='alice@example.com')
# 自動產生 __eq__
print(u1 == u2) # True
預設值
你可以像函數參數一樣給欄位指定預設值:
@dataclass
class Product:
name: str
price: float
quantity: int = 1 # 預設值
active: bool = True
p = Product("Apple", 20.0)
print(p) # Product(name='Apple', price=20.0, quantity=1, active=True)
使用 field() 自訂欄位
對於複雜的預設值(如 list 或 dict),不能直接使用 = 賦值,否則所有實例會共享同一個物件(這是 Python 的陷阱)。應該使用 field(default_factory=...):
from dataclasses import dataclass, field
from typing import List
@dataclass
class Student:
name: str
grades: List[int] = field(default_factory=list) # 正確做法
s1 = Student("Bob")
s1.grades.append(90)
s2 = Student("Charlie")
print(s2.grades) # [],不會受到 s1 影響
field() 還可以控制欄位是否參與 __init__、__repr__ 等方法的生成:
@dataclass
class Account:
username: str
password: str = field(repr=False) # 不顯示在 print 輸出中
acc = Account("alice", "secret123")
print(acc) # Account(username='alice')
__post_init__ 處理
自動產生的 __init__ 執行完後,會呼叫 __post_init__ 方法。這適合用來做額外的初始化或驗證:
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False) # 不需要傳入,稍後計算
def __post_init__(self):
self.area = self.width * self.height
rect = Rectangle(5, 3)
print(rect.area) # 15.0
不可變物件 (Frozen Instances)
設定 frozen=True 可以讓 Data Class 變成不可變的(類似 tuple):
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(10, 20)
# p.x = 30 # FrozenInstanceError: cannot assign to field 'x'
這使得物件可以用作 dictionary 的 key 或加入 set 中。
轉換為字典或元組
dataclasses 提供了輔助函數將物件轉換為標準資料結構:
from dataclasses import asdict, astuple
@dataclass
class Color:
r: int
g: int
b: int
c = Color(255, 0, 0)
print(asdict(c)) # {'r': 255, 'g': 0, 'b': 0}
print(astuple(c)) # (255, 0, 0)
繼承
Data Classes 支援繼承,子類別會包含父類別的欄位:
@dataclass
class Base:
x: int
@dataclass
class Derived(Base):
y: int
d = Derived(x=10, y=20)
print(d) # Derived(x=10, y=20)