核心篇 · Core
lv.2 核心
kp-020
dataclass 与现代数据建模
1. 一句话定义
@dataclass 装饰器根据类属性注解自动生成 __init__、__repr__、__eq__ 等样板方法,是"以数据为中心的类"的现代默认写法。
2. 为什么重要
手写 __init__ 赋值 10 个字段既啰嗦又易错。dataclass 让"数据记录"的声明只剩字段清单,同时无缝接入类型注解(kp-026);Pydantic 则在其上叠加运行时校验,是 FastAPI/LLM 生态的标配。
3. 前置知识
4. 核心概念
python
from dataclasses import dataclass, field
@dataclass
class Book:
title: str # 字段 = 注解(必须带类型注解!)
price: float
tags: list[str] = field(default_factory=list) # 可变默认值的正确姿势
summary: str = "" # 普通默认值
b1 = Book("蛇之书", 59.0)
b2 = Book("蛇之书", 59.0)
b1 == b2 # True:自动生成的 __eq__ 按字段比较
print(b1) # Book(title='蛇之书', price=59.0, tags=[], summary='')- 变体:
@dataclass(frozen=True)不可变(可哈希、防误改);slots=True(3.10+)省内存(联动 kp-034)。 - 兄弟:
typing.NamedTuple(不可变元组型记录)、typing.TypedDict(字典形状标注)。 - Pydantic:运行时校验 + 自动类型转换,
BaseModel适合"外部输入"建模。
5. 原理与机制
dataclass 读取类注解(__annotations__),生成方法代码并注入类——本质是 kp-024 装饰器与 kp-027 元编程的组合应用。"字段即注解"是关键认知:没有注解的字段不是 dataclass 字段。
python
# 验证生成结果
import dataclasses
print(dataclasses.fields(Book)) # 字段元数据可内省
print(Book.__init__.__doc__)选型心智:
text
内部纯数据记录 → @dataclass(+slots)
不可变的小记录/键 → NamedTuple 或 frozen dataclass
外部输入(HTTP/LLM) → Pydantic BaseModel(校验)
只需"形状"不必实例化 → TypedDict6. 关键事实(模型/图示)
text
@dataclass 生成的成员(按序):__init__ → __repr__ → __eq__
frozen=True 追加:__setattr__ 拦截 → __hash__ 自动可用
default_factory:可变默认值的官方解(对应 kp-007 的陷阱)7. 直观类比
手写类像手工誊写表格:每加一列都要重新画线写表头;dataclass 是电子表格——你只声明列名(字段),行操作(init/repr/eq)由软件保证一致。Pydantic 则是在电子表格上加了"数据验证规则"(年龄必须是正整数)。
8. 实例与案例
python
# 配置对象的现代写法:frozen + slots
@dataclass(frozen=True, slots=True)
class RetryConfig:
max_attempts: int = 3
backoff: float = 0.5
cfg = RetryConfig()
# cfg.max_attempts = 5 # FrozenInstanceError —— 配置不可被意外修改9. 常见误区
- 字段注解漏了类型 ——
x = 1(赋值无注解)不算字段;x: int = 1才算。 - 可变默认值写成
tags: list = []—— dataclass 会直接抛 ValueError(比 kp-007 的静默陷阱友善,但要用field(default_factory=list))。 - 把 dataclass 当业务逻辑容器 —— 它适合"数据";行为复杂、状态机式的对象仍该手写类。
- 混淆 dataclass 与 Pydantic —— 前者不校验(信你给的值),后者校验外部输入;HTTP 边界用 Pydantic。
10. 自测题
@dataclass默认生成哪三个方法?frozen=True 额外带来什么?- 为什么
tags: list[str] = []会被拒绝?正确写法? - 什么场景选 NamedTuple 而不是 dataclass?
参考答案
__init__、__repr__、__eq__;frozen 额外拦截赋值并使实例可哈希。- 共享可变默认值陷阱(同 kp-007);
field(default_factory=list)。 - 需要不可变、可解包、可比较且语义为"值"的小记录(如坐标、DB 行),且想省一点内存。
11. 与其他知识点的关系
- kp-024 装饰器:dataclass 是"会生成代码的装饰器"。
- kp-026 类型注解:字段注解是数据建模的类型化入口。
- kp-042/kp-044 Web/AI 生态:Pydantic 是两界共同的输入层。
12. 延伸阅读
- PEP 557(Dataclasses)
- Pydantic 文档:https://docs.pydantic.dev/