1. 什么是 dataclasses? #
dataclasses 是 Python 3.7+ 标准库,通过 @dataclass 装饰器自动生成 __init__、__repr__、__eq__ 等方法。
- 适合定义"只存数据"的类,如配置对象、DTO、API 请求/响应模型,避免手写大量样板代码。
- 字段必须用类型注解声明,与类型提示配合使用。
- 日常开发中,遇到"这个类主要是装几个字段"的场景,优先考虑
dataclass。
2. 为什么用 dataclass? #
- 普通类定义数据结构需要手写
__init__、__repr__、__eq__,代码冗长且容易遗漏。
- dataclass 用装饰器一行搞定,字段声明即结构定义,可读性更好。
- 支持默认值、
default_factory、不可变(frozen)、排序(order)等实用特性。
- 比
namedtuple 更灵活(默认可变、支持自定义方法),比手写普通类更简洁。
| 对比 |
dataclass |
普通类 |
| 代码量 |
少,自动生成方法 |
多,需手写 |
| 类型注解 |
强制,结构清晰 |
常被忽略 |
| 可变性 |
默认可变,可选 frozen |
完全自定义 |
3. 快速上手 #
- 用
@dataclass 装饰类,用类型注解声明字段,无需手写 __init__。
- 创建实例时按字段顺序传参,或按字段名传关键字参数。
- 自动生成
__repr__(print 友好输出)和 __eq__(按字段值比较)。
- 需要排序时加
order=True;需要不可变时加 frozen=True。
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)
print(p1)
print(p1 == p2)
自动生成的方法:
| 方法 |
作用 |
默认 |
__init__ |
构造实例 |
生成 |
__repr__ |
打印展示 |
生成 |
__eq__ |
等值比较 |
生成 |
__lt__ 等 |
排序比较 |
需 order=True |
class Point:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
def __eq__(self, other):
if not isinstance(other, Point):
return False
return self.x == other.x and self.y == other.y
p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)
print(p1)
print(p1 == p2)
4. 字段与默认值 #
- 简单默认值直接写
age: int = 20,有默认值的字段必须放在无默认值字段之后。
- 列表、字典等可变类型的默认值必须用
field(default_factory=list),不能直接写 [] 或 {}。
from dataclasses import dataclass, field
@dataclass
class Person:
name: str
age: int = 20
tags: list[str] = field(default_factory=list)
field() 常用参数:
| 参数 |
说明 |
示例 |
default |
简单默认值 |
field(default=0) |
default_factory |
可变类型默认值 |
field(default_factory=list) |
init=False |
不参与构造参数 |
field(init=False) |
repr=False |
不显示在 print 中 |
field(repr=False) |
5. post_init 与数据校验 #
field(init=False) 表示该字段不出现在 __init__ 参数中,适合由 __post_init__ 计算得出。
field(repr=False) 可隐藏敏感字段(如密码)不出现在 print 输出中。
__post_init__ 在自动生成的 __init__ 执行完毕后调用,用于数据校验或计算派生字段。
- 校验逻辑(如价格不能为负、名称不能为空)应放在
__post_init__,而不是手写 __init__。
- 配合
field(init=False) 可在 __post_init__ 中计算派生属性,如根据宽高计算面积。
from dataclasses import dataclass, field
@dataclass
class Person:
name: str
age: int = 20
tags: list[str] = field(default_factory=list)
password: str = field(default="", repr=False)
name_length: int = field(init=False)
def __post_init__(self):
self.name_length = len(self.name)
p1 = Person("Tom", password="secret", tags=["hello"])
print(p1)
6. 转换与复制 #
asdict(obj) 将实例转为字典,常用于 JSON 序列化或日志输出。
replace(obj, **changes) 基于已有实例创建修改了部分字段的新副本,原实例不变。
from dataclasses import dataclass, asdict, replace
@dataclass
class Point:
x: int
y: int
p = Point(1, 2)
print(asdict(p))
p2 = replace(p, x=3)
print(p2)
7. 高级特性 #
frozen=True 使实例不可变,适合配置常量、坐标等值类型,也可作为 dict 的 key。
order=True 自动生成比较方法,实例可直接用于 sorted()、min()、max()。
- 继承在 dataclass 中与普通类一样可用,父类字段在前、子类字段在后,日常项目中使用较少。
- 以下两个特性按需使用,不是每个 dataclass 都需要。
7.1 不可变实例(frozen) #
@dataclass(frozen=True)
class ImmutablePoint:
x: int
y: int
p = ImmutablePoint(1, 2)
7.2 排序支持(order) #
from dataclasses import field
from dataclasses import dataclass
@dataclass(order=True)
class Task:
priority: int
name: str = field(compare=False)
print(sorted([Task(2, "B"), Task(1, "A")]))
8. 实战示例 #
- 下面用 dataclass 定义数据库配置和 API 响应,是项目中最常见的两类用法。
- 配置类用默认值表达常用配置,创建时只覆盖需要修改的字段。
- API 响应类统一接口返回格式,配合类型注解让 IDE 能正确补全
data 字段。
- 复杂校验放在
__post_init__,简单场景也可依赖类型注解和默认值。
8.1 配置对象 #
from dataclasses import dataclass
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
username: str = "admin"
password: str = ""
database: str = "app_db"
config = DatabaseConfig(
host="db.example.com",
username="app_user",
password="secret"
)
print(config)
8.2 API 响应封装 #
from dataclasses import dataclass
@dataclass
class ApiResponse:
success: bool
data: dict
message: str = ""
code: int = 200
resp = ApiResponse(
success=True,
data={"user_id": 123, "name": "Alice"},
message="获取成功"
)
print(resp)
9. 常见错误 #
- 可变默认值直接写
[] 或 {} 会导致所有实例共享同一对象,必须用 default_factory。
- 忘记类型注解会让 dataclass 失去结构清晰的优势,IDE 也无法正确推断。
- 有默认值的字段放在无默认值字段前面会报
TypeError,顺序与函数参数规则相同。
- 数据校验逻辑写在手写
__init__ 中会与 dataclass 冲突,应使用 __post_init__。
@dataclass
class Bad:
items: list = []
@dataclass
class Good:
items: list = field(default_factory=list)
10. 总结 #
- dataclass 适合配置对象、DTO、API 模型等"以数据为主"的类,是减少样板代码的首选方案。
- 核心掌握:
@dataclass、类型注解、默认值、default_factory、__post_init__、asdict/replace。
- 按需使用
frozen=True(不可变)和 order=True(排序),不必每个类都加。
10.1 常用写法速查 #
from dataclasses import dataclass, field, asdict, replace
@dataclass
class User:
id: int
name: str
tags: list[str] = field(default_factory=list)
_token: str = field(default="", repr=False, init=False)
def __post_init__(self):
if not self.name:
raise ValueError("name 不能为空")
@dataclass(frozen=True)
class Config:
debug: bool = False
timeout: int = 30
10.2 最佳实践 #
- 字段用类型注解,可变默认值用
default_factory
- 校验逻辑放
__post_init__,不手写 __init__
- 序列化用
asdict,不可变对象修改用 replace
- 敏感字段设
repr=False,避免打印时泄露