Python/dataclass
外觀
< Python
在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)