dataclass:别再手写一堆 __init__ 了
用标准库 @dataclass 自动生成 __init__/__repr__/__eq__;可变字段默认值请用 field(default_factory=...),别再写 []。
要解决的问题
写 Python 后端时,经常要定义「就几个字段」的数据类:购物车、配置片段、API 请求体、任务队列里的一条消息。很多人会手写 __init__、__repr__、__eq__,字段一多就重复,改一个字段还要改三处,特别容易漏。更糟的是,为了省事在类属性或函数参数里写 items = [],几个实例会悄悄共用同一份列表——表面上各自 add,调试时却发现「别人」的数据也进来了。
标准库模块 dataclasses 从 Python 3.7 起可用(见 PEP 557)。加一个 @dataclass,就能按类型注解生成 __init__、__repr__、__eq__ 等常用方法。可变字段的默认值要用 field(default_factory=...),不要直接写 [] / {}。本文示例已在本机 Python 3.13.5 验证;3.12 及以上写法一致,老一点的 3.7–3.11 也可用同一套核心 API(个别参数如 slots、kw_only 是后续版本才加的)。
核心概念
日常先记住这几条:
@dataclass:装饰器扫描带类型注解的字段,按定义顺序生成方法。默认会生成__init__、__repr__、__eq__(见官方文档「Module contents」)。类上已有同名方法时,对应开关会被忽略或按文档规则处理。- 字段注解:
name: str、quantity: int = 0。有默认值的字段必须排在无默认值字段后面,否则构造函数签名不合法,会TypeError。 field(default_factory=...):default_factory必须是零参数可调用对象;需要默认值时调用它,得到新对象。官方例子:mylist: list[int] = field(default_factory=list)。不能同时写default和default_factory。- 可变默认值:普通类把可变对象放在类属性上,多实例会共享。
@dataclass若检测到不可哈希的默认值(常用来近似「可变」),会直接ValueError,逼你改用default_factory(3.11 起按「不可哈希」判断,见文档「Mutable default values」)。 asdict/replace:需要转成字典、或基于现有实例改个别字段得到新对象时可用;嵌套结构与浅拷贝注意点以官方文档为准,本文不展开。
完整说明:dataclasses — Data Classes(Python 3.12)。设计动机与目标见 PEP 557。
示例代码
手写版和 dataclass 版对比(本地已跑通):
from dataclasses import dataclass, field
# 手写:啰嗦,还容易漏 __repr__ / __eq__
class CartManual:
def __init__(self, user: str, items: list[str] | None = None):
self.user = user
# 注意:默认不要写 items=[],那是所有调用共享的同一列表
self.items = [] if items is None else items
def add(self, name: str) -> None:
self.items.append(name)
@dataclass
class Cart:
user: str
items: list[str] = field(default_factory=list)
def add(self, name: str) -> None:
self.items.append(name)
a = Cart("alice")
b = Cart("bob")
a.add("apple")
assert a.items == ["apple"]
assert b.items == [] # 两个实例列表互不影响
assert a.items is not b.items
assert a == Cart("alice", ["apple"]) # 自动生成的 __eq__
print(a) # Cart(user='alice', items=['apple'])
官方文档「Mutable default values」一节用普通类演示了共享类属性列表的问题(大意是:o1 和 o2 的 x 其实是同一个对象)。若在 dataclass 里写成 x: list = [],装饰器会拦住,本机报错类似:
ValueError: mutable default <class 'list'> for field x is not allowed: use default_factory
正确写法就是上面的 field(default_factory=list)。函数参数同样不要写 def f(xs=[]),应写成 xs: list[str] | None = None,在函数体内再 xs = [] if xs is None else xs。
需要把实例变成可 JSON 序列化的字典时,可以用 dataclasses.asdict(a);只改一个字段得到新实例,可以用 dataclasses.replace(a, user="carol")。二者都会按文档做递归或重新走 __init__,用前先读一眼对应小节。
常见误区
-
items: list = []或类属性x = []
多实例会共享同一列表,改一个等于改全家。dataclass 多数情况会拦住不可哈希默认值;仍应养成default_factory的习惯。 -
有默认值的字段写在无默认值字段前面
会TypeError。先写必填,再写带默认值的。继承时父类与子类字段合并后也要满足这一规则。 -
以为
@dataclass会「冻结」对象
默认实例仍可变。需要近似只读时用@dataclass(frozen=True)(赋值会触发FrozenInstanceError),细节见官方「Frozen instances」。注意 frozen 与eq组合还会影响能否自动生成__hash__。 -
用 dataclass 硬扛复杂行为
需要依赖其他字段做二次初始化时,可以写__post_init__;复杂继承、描述符字段、InitVar等进阶用法请直接查文档,不要凭感觉拼。字段很多、行为很重的领域模型,也可以继续用手写类或 Pydantic 等方案,dataclass 不是唯一答案。
小结
「就几个字段」的数据容器,优先 @dataclass:少写样板代码,__repr__ / __eq__ 还不容易漏。可变字段的默认值一律 field(default_factory=...),别在类属性或函数默认参数里塞 [] / {}。本文按 Python 3.12 dataclasses 文档 与 PEP 557 核对;正式项目以你当前解释器版本的官方文档为准。
参考资料
谢谢你读到这里。