dataclass
dataclasses 是 Python 标准库,用来定义“主要负责保存数据”的类。它会根据类型注解自动生成构造函数、调试友好的字符串表示和比较方法,让数据对象少写一些样板代码。
1 | from dataclasses import dataclass |
@dataclass只处理带有类型注解的字段。类型注解既是文档,也决定了哪些类属性会被当作数据字段。
基础使用
1. 定义字段与默认值
字段必须先写无默认值,再写有默认值的字段,否则生成 __init__ 时会违反 Python 参数规则。
1 | from dataclasses import dataclass |
实例会自动获得:
__init__:按字段顺序接收参数。__repr__:输出Article(title=..., author=..., published=...)。__eq__:同类型实例按字段值比较。
2. 可变默认值要用 default_factory
不要把列表、字典或集合直接写成默认值。否则多个实例可能共享同一个对象;使用 field(default_factory=...) 才会为每个实例创建一份独立值。
1 | from dataclasses import dataclass, field |
3. field() 控制字段行为
1 | from dataclasses import dataclass, field |
常用参数:
| 参数 | 作用 |
|---|---|
default |
提供一个固定默认值。 |
default_factory |
调用工厂函数生成默认值,适合列表、字典等可变对象。 |
init=False |
不把字段放进自动生成的 __init__。 |
repr=False |
不在 repr(instance) 中显示字段,适合密码等敏感值。 |
compare=False |
比较实例时忽略该字段。 |
初始化与校验
1. 用 __post_init__ 处理派生值
__post_init__ 会在自动生成的 __init__ 结束后执行,适合校验参数或根据输入计算字段。
1 | from dataclasses import dataclass, field |
2. 使用 InitVar 接收一次性参数
InitVar 会出现在构造函数中,也会传给 __post_init__,但不会成为实例字段。它适合密码、原始配置等只在初始化阶段需要的值。
1 | from dataclasses import InitVar, dataclass, field |
3. ClassVar 不会成为实例字段
类级别的常量应标注为 ClassVar,这样 dataclass 不会把它加入构造函数、比较和 fields() 结果。
1 | from dataclasses import dataclass |
不可变对象与高级选项
1. frozen=True 创建不可变数据对象
冻结后不能直接修改字段,适合配置、坐标、字典键等值对象。需要修改时,应创建新实例,而不是绕过限制强行赋值。
1 | from dataclasses import dataclass |
冻结类通常更适合参与哈希和集合操作,但只有在字段本身也可哈希时才适合作为字典键。
2. 关键字参数与 slots
1 | from dataclasses import dataclass |
kw_only=True要求字段使用关键字传入,参数含义更清楚,也能减少后续新增字段造成的调用破坏。slots=True使用__slots__存储字段,通常能减少对象开销,同时阻止随意添加未声明的属性。
这两个选项需要较新的 Python 版本;如果项目需要兼容旧版本,应先确认运行环境。
3. 继承时注意字段顺序
父类已有默认字段时,子类再添加无默认字段可能触发 TypeError: non-default argument follows default argument。常见解决方式是让新字段成为关键字参数,或重新设计基类的字段顺序。
转换、复制与检查
1 | from dataclasses import asdict, astuple, fields, is_dataclass, replace |
asdict()递归转换嵌套 dataclass,并会复制部分容器;适合生成普通字典,但不等同于完整 JSON 序列化。astuple()以元组形式递归转换。fields()返回声明过的字段信息,可用于通用校验或表格渲染。replace()创建一个新实例,并用指定值覆盖字段;冻结对象也应通过它更新。is_dataclass()可判断类或实例是否由@dataclass定义。
转成 JSON
标准库没有为所有类型自动提供 JSON 编码器。普通字段可以先用 asdict() 转换,再交给 json.dumps();datetime、Path 等特殊类型仍需自定义转换。
1 | import json |
一个更完整的实践例子
下面用一个页面配置对象串起默认值、校验、派生字段和序列化:
1 | from dataclasses import asdict, dataclass, field |
使用建议
- 数据类只表达数据和轻量的领域行为;复杂业务流程应放在服务函数或独立类中。
- 任何可变默认值都优先使用
default_factory。 - 对外部输入在
__post_init__中尽早校验,让无效对象无法继续传播。 - 配置和不可变值对象可考虑
frozen=True;需要明确调用语义时可考虑kw_only=True。 asdict()适合简单转换,不要把它当作所有类型的通用序列化方案。