装饰器 · dataclass

0. 一句话定位

维度 内容
作用对象
使用场景 元编程
来源 标准库 dataclasses(3.7+)
语法形式 @dataclass / @dataclass(frozen=True, slots=True, ...)

段末注释:数据类(data class) 以存储字段为主、行为为辅的类;@dataclass 按字段声明自动生成构造与常用 dunder 方法。

1. 做什么

根据类属性类型注解生成 __init____repr____eq__ 等;可选生成排序、__hash__、不可变实例、__slots__。减少手写样板,保持与普通 class 相同的使用方式。

2. 重点参数

参数 类型 默认值 作用 配置建议
init bool True 是否生成 __init__ 需完全自定义构造时设 False
repr bool True 是否生成 __repr__ 调试友好,一般保持
eq bool True 是否生成 __eq__ 按字段值比较
order bool False 是否生成 <<= 需排序时开启;字段应可比较
unsafe_hash bool False 可变类也强制 __hash__ 默认仅 frozen=True 时有 hash
frozen bool False 实例不可变(赋值抛异常) 作 dict key / 进 set 时设 True
slots bool False 使用 __slots__(3.10+) 减内存、禁止动态属性
kw_only bool False 字段仅关键字传参(3.10+) API 字段多时避免位置参数错位
match_args bool True 支持 match 解构(3.10+) 用结构模式匹配时保持

字段级field(default=...)field(default_factory=list)field(init=False)field(repr=False)field(compare=False)field(hash=False)

3. 最小可运行示例

1
2
3
4
5
6
7
8
9
10
from dataclasses import dataclass

@dataclass
class Point:
x: float
y: float

p = Point(1.0, 2.0)
print(p) # Point(x=1.0, y=2.0)
print(p == Point(1.0, 2.0)) # True

带默认值与 default_factory

1
2
3
4
5
6
7
8
9
10
11
from dataclasses import dataclass, field

@dataclass
class User:
name: str
tags: list[str] = field(default_factory=list)

u1 = User("alice")
u1.tags.append("admin")
u2 = User("bob")
print(u2.tags) # [],不会共享 u1 的 list

不可变 + 可哈希:

1
2
3
4
5
6
7
8
@dataclass(frozen=True)
class ImmutablePoint:
x: int
y: int

p = ImmutablePoint(1, 2)
s = {p} # OK
# p.x = 3 # FrozenInstanceError

4. 常见变体

继承

1
2
3
4
5
6
7
8
@dataclass
class Base:
id: int

@dataclass
class Item(Base):
name: str
# 子类字段追加在父类字段之后

init=False 的计算字段

1
2
3
4
5
6
7
8
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False)

def __post_init__(self):
self.area = self.width * self.height

slots=True(3.10+)

1
2
3
@dataclass(slots=True)
class Light:
value: int

关键字专用字段(3.10+)

1
2
3
4
5
6
@dataclass(kw_only=True)
class Config:
host: str = "localhost"
port: int = 8080

Config(port=9000) # host 可省略

转换:asdict / astuple

1
2
3
4
from dataclasses import asdict, astuple

asdict(p) # {'x': 1.0, 'y': 2.0}
astuple(p) # (1.0, 2.0)

替代 namedtuple

1
2
3
4
5
@dataclass(frozen=True)
class Row:
name: str
score: int
# 可变、可扩展,比 namedtuple 灵活

5. 适用 / 不适用

适用

  • DTO、配置对象、API 请求/响应模型(轻量,无 ORM)
  • 需要清晰字段列表 + 自动 repr/eq 的结构体
  • 不可变值对象(frozen=True

不适用

  • 复杂 ORM 实体(SQLAlchemy 等自有模型体系)
  • 大量自定义 dunder、复杂继承层次 → 普通 class 或 pydantic BaseModel
  • 需运行时动态增删字段 → 不用 slots=True,慎用 dataclass

6. 易踩坑

  • 可变默认值:必须 field(default_factory=list),禁止 tags: list = []
  • frozen=True:不能在 __init__ 外赋值;需在构造时算出的字段用 __post_init__ + object.__setattr__(self, 'field', val)
  • order=True:所有参与比较的字段须支持 <;混类型可能 TypeError
  • __hash__:默认可变 dataclass 无 hash;要进 set 用 frozen=Trueunsafe_hash=True(后者对可变实例有风险)
  • 继承顺序:父类若非 dataclass,子类 @dataclass 仍可用,但 MRO 与字段顺序需自检
  • 与 pydantic:dataclass 不做运行时类型校验;要严格校验用 pydantic @dataclassBaseModel

7. 近邻替代

替代 何时用
typing.NamedTuple 轻量不可变、元组语义
pydantic.BaseModel 校验、序列化、JSON schema
attrs 更丰富的装饰器生态(3.7 前或高级特性)
手写 __init__ 构造逻辑极复杂

8. 参考

-------------本文结束感谢您的阅读-------------