dataclass中可变默认值(如[]、{}、set())会导致所有实例共享同一对象,应改用field(default_factory=list)等安全方式;继承时还可能引发字段顺序错误。

直接用 []、{} 或 set() 作为 dataclass 字段默认值,会导致所有实例共享同一对象 —— 这不是 bug,是 Python 的语言机制,但极易引发隐蔽的数据污染。
可变默认值在类定义时就被创建一次
Python 中函数和类的默认参数在定义时求值,而非每次调用/实例化时重新生成。dataclass 字段的默认值也遵循这一规则。
这意味着:
-
items: list = []中的空列表,在@dataclass装饰器运行时(即模块加载时)就已创建完毕,之后所有MyClass()实例都引用它 - 修改任一实例的
items,其他实例看到的也是同一个被改过的列表 - 这种共享发生在类级别,和
__init__是否执行无关
field(default_factory=list) 是唯一安全的替代方案
default_factory 接收一个无参可调用对象(如 list、dict、自定义函数),并在每次实例化时调用它,确保每个实例获得全新对象。
常见写法对比:
- ❌ 危险:
config: dict = {}→ 所有实例共用一个字典 - ✅ 安全:
config: dict = field(default_factory=dict)→ 每次新建独立字典 - ✅ 安全:
tags: set = field(default_factory=set) - ✅ 安全:
metadata: dict = field(default_factory=lambda: {"created": True})
注意:default_factory 必须是可调用对象,不能是调用结果(比如 field(default_factory=dict()) 是错的,会立即执行并复用返回值)。
继承场景下默认值顺序冲突会放大问题
当父类字段使用可变默认值,又和子类字段混合时,不仅有共享风险,还可能触发 TypeError: non-default argument follows default argument。
例如:
- 父类定义
name: str = "anon"(带默认值),age: int(不带) - 子类再加
grade: int,dataclass 合并字段后顺序变成name→age→grade,违反“无默认值字段必须在前”的语法约束 - 此时即使没数据污染,代码也无法运行
统一用 field(default_factory=...) 可规避该问题:它本质是为字段提供“带默认行为的无默认值字段”,既安全,又不破坏字段排序规则。
最易被忽略的一点:这个机制和你是否显式写了 __init__ 无关,只要用了 @dataclass,默认值绑定就在类定义阶段完成 —— 看不见,但一直存在。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











