跳至內容

Python/dataclass

維基教科書,自由的教學讀本

在Python編程中,我們經常需要定義一些「數據容器」——它們主要用來存儲數據,而不是實現複雜的業務邏輯。比如配置文件、API請求參數、實驗數據等。傳統的做法是定義一個類,然後寫一堆重複的代碼:

# 传统方式:繁琐且容易出错
class Person:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email
    
    def __repr__(self):
        return f"Person(name={self.name}, age={self.age}, email={self.email})"
    
    def __eq__(self, other):
        if not isinstance(other, Person):
            return False
        return self.name == other.name and self.age == other.age and self.email == other.email

這種寫法有很多問題:

  • 重複代碼多:每個類都要寫 __init__、__repr__、__eq__ 等方法
  • 容易出錯:忘記在 __eq__ 中添加新字段
  • 沒有類型檢查:傳入錯誤類型也不會報錯
  • 難以維護:添加新字段需要修改多處

為了解決這些問題,Python社區提供了兩個優秀的解決方案:dataclass(標準庫)和 pydantic(第三方庫)。在 dataclass 出現之前,開發者們常常使用 namedtuple 或手寫大量重複代碼。

與pydantic相比,dataclass的性能要強3-7倍以上,適合單純的大量數據的容器。例如定義一個Point數據類,保存幾百萬個Point。

Dataclass 的誕生

[編輯]

2016年的Python 3.6引入了「變量註解」(viriable annotation)寫法,即:

var1: int = 101

2018年的Python 3.7為了減少樣板代碼,讓數據類的定義更簡潔,引入了dataclass。其設計目標是:「讓定義數據類像寫配置文件一樣簡單」

dataclass特性:

  • 自動生成常用方法(__init__、__repr__、__eq__ 等)
  • 使用類型註解來聲明字段
  • 零依賴,標準庫自帶
  • 性能優秀

基礎用法

[編輯]
from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int
    grade: float
    is_active: bool = True  # 默认值

# 创建实例
student = Student(name="张三", age=20, grade=85.5)

print(student)
# 输出: Student(name='张三', age=20, grade=85.5, is_active=True)

# 自动生成的 __eq__ 方法
student2 = Student(name="张三", age=20, grade=85.5)
print(student == student2)  # True

沒有數據驗證功能

[編輯]
# Dataclass:只是注解,不验证
from dataclasses import dataclass

@dataclass
class DataclassConfig:
    learning_rate: float
    epochs: int

# ❌ 不会报错,但类型错了
config = DataclassConfig(learning_rate="不是浮点数", epochs="不是整数")
print(config.learning_rate)  # "不是浮点数"
print(type(config.learning_rate))  # <class 'str'>

自動生成的方法

[編輯]
  • __init__:構造函數。注意,對於未賦缺省值的字段,如果在構造函數沒有通過命名參數或者位置參數賦值初始化,則報錯 TypeError: missing required argument 'name'
  • __repr__:字符串表示
  • __eq__:相等性比較
  • __hash__(可選):哈希值

默認值和工廠函數

[編輯]
from dataclasses import dataclass, field
from typing import List

@dataclass
class Course:
    name: str
    credits: int
    # ❌ 错误:可变默认值会被所有实例共享
    # students: List[str] = []  # 危险!
    
    # ✅ 正确:使用 field 和 default_factory
    students: List[str] = field(default_factory=list)
    metadata: dict = field(default_factory=dict)
    layers: List[int] = field(default_factory=lambda: [128, 64])

course1 = Course(name="数学", credits=4)
course2 = Course(name="物理", credits=3)

course1.students.append("张三")
print(course1.students)  # ['张三']
print(course2.students)  # [] ✅ 独立的列表

高級特性

[編輯]
from dataclasses import dataclass, field

@dataclass(frozen=True)  # 不可变对象
class Point:
    x: float
    y: float

p = Point(1.0, 2.0)
# p.x = 3.0  # ❌ 错误:FrozenInstanceError

@dataclass(order=True)  # 支持比较运算。dataclass 会自动生成以下方法(相当于手动实现了这些,按照字段声明先后做“天然比较顺序”):
# __lt__(<)
# __le__(<=)
# __gt__(>)
# __ge__(>=)
# __eq__(==)(默认就有)
# __ne__(!=)(默认就有)
class Score:
    value: int
    
scores = [Score(85), Score(92), Score(78)]
print(sorted(scores))  # 按 value 排序

@dataclass
class Config:
    name: str
    # 不包含在 __init__ 中,但会被初始化
    timestamp: str = field(init=False)
    
    def __post_init__(self):
        from datetime import datetime
        self.timestamp = datetime.now().isoformat()

config = Config(name="test")
print(config.timestamp)  # 自动生成的时间戳

轉換為字典

[編輯]
from dataclasses import dataclass, asdict, astuple

@dataclass
class Book:
    title: str
    author: str
    year: int

book = Book("Python编程", "张三", 2023)

# 转为字典
print(asdict(book))
# {'title': 'Python编程', 'author': '张三', 'year': 2023}

# 转为元组
print(astuple(book))
# ('Python编程', '张三', 2023)