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)