Python Dataclass 實用技巧全攻略
6 分鐘閱讀 1,000 字
Python Dataclass 實用技巧全攻略
Python 3.7 引入的 dataclasses 模組讓定義資料容器類別變得更加簡潔。不再需要手動撰寫 __init__、__repr__、__eq__ 等樣板程式碼。但 dataclass 的功能遠不止如此,本文將帶你深入探索各種實用技巧。
基礎回顧
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class User:
name: str
email: str
age: int = 0
tags: list[str] = field(default_factory=list)
bio: Optional[str] = None
# 自動生成 __init__, __repr__, __eq__
user = User(name="Alice", email="alice@example.com", age=28)
print(user) # User(name='Alice', email='alice@example.com', age=28, tags=[], bio=None)注意:帶有預設值的欄位必須放在沒有預設值的欄位之後。可變型別(list、dict)必須用
field(default_factory=...)而不是直接賦值。
技巧一:`__post_init__` 做資料驗證
from dataclasses import dataclass
from datetime import date
@dataclass
class Employee:
name: str
salary: float
start_date: date
def __post_init__(self):
if self.salary < 0:
raise ValueError(f"薪資不能為負數:{self.salary}")
if self.start_date > date.today():
raise ValueError("入職日期不能是未來")
# 自動正規化
self.name = self.name.strip().title()
emp = Employee(name=" bob smith ", salary=50000, start_date=date(2024, 1, 15))
print(emp.name) # Bob Smith技巧二:凍結不可變物件
@dataclass(frozen=True)
class Point:
x: float
y: float
def distance_to(self, other: "Point") -> float:
return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5
p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance_to(p2)) # 5.0
# frozen=True 讓物件可以作為字典的 key 或放進 set
points = {p1, p2}技巧三:`field` 的進階用法
from dataclasses import dataclass, field
from typing import ClassVar
import uuid
@dataclass
class Order:
# ClassVar 不會被納入 __init__ 的參數
_counter: ClassVar[int] = 0
product: str
quantity: int
# repr=False:不出現在 __repr__ 輸出中
internal_id: str = field(default_factory=lambda: str(uuid.uuid4()), repr=False)
# compare=False:不參與 __eq__ 比較
created_at: float = field(default_factory=lambda: __import__('time').time(), compare=False)
# init=False:不能在建構子中傳入,只能由 __post_init__ 設定
total_price: float = field(init=False)
unit_price: float = 0.0
def __post_init__(self):
self.total_price = self.unit_price * self.quantity技巧四:繼承與擴展
@dataclass
class Base:
name: str
created_at: str = field(default_factory=lambda: __import__('datetime').datetime.now().isoformat())
@dataclass
class Article(Base):
title: str = ""
body: str = ""
is_published: bool = False
def publish(self):
self.is_published = True
return self
article = Article(name="tech-blog", title="Hello World", body="...")
article.publish()技巧五:與 `asdict` 和 `astuple` 互轉
from dataclasses import dataclass, asdict, astuple
@dataclass
class Config:
host: str
port: int
debug: bool = False
config = Config(host="localhost", port=8080)
# 轉成字典(常用於 JSON 序列化)
config_dict = asdict(config)
print(config_dict) # {'host': 'localhost', 'port': 8080, 'debug': False}
import json
print(json.dumps(config_dict)) # 直接序列化
# 從字典還原
new_config = Config(**config_dict)
# 轉成 tuple
print(astuple(config)) # ('localhost', 8080, False)技巧六:`replace` 做不可變更新
類似 Rust 的 struct update syntax,dataclasses.replace 讓你基於現有物件建立修改版本:
from dataclasses import replace
@dataclass(frozen=True)
class AppState:
user_id: Optional[int] = None
theme: str = "light"
language: str = "zh-TW"
sidebar_open: bool = True
state = AppState(user_id=42)
# 建立修改後的新狀態,不改動原始物件
new_state = replace(state, theme="dark", sidebar_open=False)
print(state.theme) # light
print(new_state.theme) # dark技巧七:搭配 `__slots__` 節省記憶體
Python 3.10 起,dataclass 支援 slots=True,自動加上 __slots__ 以節省記憶體:
@dataclass(slots=True)
class Particle:
x: float
y: float
z: float
mass: float
# 大量建立時記憶體用量明顯更少
particles = [Particle(i, i*2, i*3, 1.0) for i in range(100_000)]技巧八:自訂排序
@dataclass(order=True)
class Student:
# order=True 會按欄位順序比較
# 用 field(compare=False) 排除不想比較的欄位
gpa: float
name: str = field(compare=False)
student_id: int = field(compare=False)
students = [
Student(gpa=3.8, name="Alice", student_id=1),
Student(gpa=3.5, name="Bob", student_id=2),
Student(gpa=3.9, name="Charlie", student_id=3),
]
# 按 GPA 排序
print(sorted(students, reverse=True))Dataclass vs Pydantic
| 特性 | Dataclass | Pydantic |
|---|---|---|
| 執行時型別驗證 | 無 | 有 |
| JSON 序列化 | 需手動 | 內建 |
| 效能 | 較快 | 稍慢 |
| 學習曲線 | 低 | 中 |
| 適合場景 | 純資料容器 | API 模型、設定檔 |
小結
Dataclass 是 Python 內建的輕量資料容器解決方案。掌握 __post_init__、frozen、field 的各種參數、replace、slots 等功能,足以應對大多數的資料建模需求。當需要執行時型別驗證或複雜序列化時,再考慮引入 Pydantic。
分享這篇文章