dataclasses 的可变默认值会引发共享引用问题,因所有实例共用同一对象;应使用 field(default_factory=list) 每次生成新对象;init=false 字段需有默认值;继承时非默认字段须在默认字段前;asdict() 比 dict 更完整。

dataclasses 能省掉大量样板代码,但直接套用 @dataclass 很容易踩坑——比如可变默认值、继承行为异常、或序列化时字段丢失。
为什么 field(default=[]) 会引发共享引用问题
dataclass 不允许对可变对象(如 []、{})直接设为默认值,否则所有实例会共用同一个对象。
错误写法:
@dataclass
class User:
tags: list = [] # ❌ 危险!所有 User() 实例共享同一列表
正确做法是用 field(default_factory=list):
-
default_factory必须是可调用对象,每次实例化时都会重新调用生成新对象 - 常见工厂函数:
list、dict、lambda: [1, 2]、自定义函数 - 不能混用
default和default_factory,否则报ValueError
如何控制哪些字段参与 __init__ 或 __repr__
不是所有字段都需要出现在初始化参数里,比如缓存、计算属性或内部状态。
用 field(init=False) 可跳过 __init__,但依然保留在 __repr__ 中;加 repr=False 才彻底隐藏:
@dataclass
class CacheItem:
key: str
value: str
updated_at: datetime = field(init=False, default_factory=datetime.now)
_cache_hit_count: int = field(repr=False, default=0) # 不显示在 repr 里
-
init=False的字段必须有默认值(含default_factory),否则实例化失败 - 若同时设
init=False和default=...,该字段不会出现在__init__签名中,但会被赋值 -
compare=False可排除字段参与==比较,hash=False影响哈希行为
继承 dataclass 时字段顺序和 __post_init__ 的执行时机
子类 dataclass 会自动合并父类字段,但顺序按 MRO(方法解析顺序)拼接,且所有字段必须在非默认字段之后——否则抛 TypeError。
例如:
@dataclass
class Animal:
name: str
<p>@dataclass
class Dog(Animal):
breed: str
age: int = 0 # ✅ 合法:非默认字段在前,带默认值的在后
</p>
- 如果父类有默认字段,子类新增非默认字段,就会报错(违反“非默认字段不能跟在默认字段后”规则)
-
__post_init__在所有字段赋值完成后调用,父子类都会执行;子类需显式调用super().__post_init__(),否则父类逻辑被跳过 - 避免在
__post_init__中修改field(init=False)字段以外的字段,可能干扰冻结(frozen=True)行为
序列化时字段缺失?检查 __dict__ 和 asdict() 的差异
直接访问 obj.__dict__ 可能漏掉由 field(init=False) 创建的字段(它们不进 __dict__),而 asdict() 是安全的。
示例:
from dataclasses import asdict <p>item = CacheItem(key="x", value="y") print(item.<strong>dict</strong>) # 不含 _cache_hit_count(因 repr=False 不影响 <strong>dict</strong>,但 init=False 字段也不在此) print(asdict(item)) # 包含全部字段,包括 init=False 的 updated_at 和 _cache_hit_count </p>
-
asdict()递归处理嵌套 dataclass,但对自定义类或不可序列化对象(如datetime)会原样保留,需额外转换 - 需要 JSON 序列化时,别直接
json.dumps(obj),会报TypeError;应先asdict(),再对datetime等做default=...处理 - 如果用了
frozen=True,asdict()仍可用,但无法在__post_init__中给字段重新赋值
真正麻烦的是字段校验和类型运行时检查——@dataclass 本身不做任何类型验证,type: str 只是注解。需要强约束就得上 pydantic 或手动在 __post_init__ 里写 isinstance 判断。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











