可变默认值必须用 default_factory,default 只适用于不可变值;default_factory 要传可调用对象(如 list),而非调用结果(如 list()),否则引发对象共享问题。

dataclass 中 default 和 default_factory 不能混用
直接给可变对象(比如 list、dict)设 default=[],所有实例会共享同一个对象——这是 Python 函数默认参数陷阱的翻版,dataclass 照单全收。
真正安全的做法是:**可变默认值必须用 default_factory**,它在每次实例化时调用一次工厂函数,生成新对象。
-
default只能用于不可变值(None、42、"hello")或你明确想共享的对象 -
default_factory接收一个无参可调用对象,比如list、dict、lambda: [],不能带括号调用(写成list()就错了) - 如果误把
default_factory=list()当成工厂,实际传的是空列表实例,反而触发共享问题
为什么 field(default_factory=list) 没报错但 field(default_factory=list()) 会出 bug
default_factory 参数要的是“怎么造”,不是“造好的东西”。传 list 是告诉 dataclass:“每次需要时调用 list()”;传 list() 是直接执行了一次,把返回的空列表当成了默认值,等效于 default=[]。
典型错误现象:两个实例修改各自的 items 列表,结果互相影响。
立即学习“Python免费学习笔记(深入)”;
from dataclasses import dataclass, field <p>@dataclass class BadExample: items: list = field(default_factory=list()) # ❌ 错!这里 list() 立即执行</p><p>@dataclass class GoodExample: items: list = field(default_factory=list) # ✅ 对!传的是构造器本身</p>
嵌套 dataclass 或自定义类怎么设默认工厂
如果字段类型本身不是内置可调用对象(比如你自己写的 User 类),就不能直接写 default_factory=User ——除非 User() 真的不带参数。一旦需要传参或做初始化逻辑,就得用 lambda 包一层。
- 无参构造:直接写
default_factory=User - 需传参:用
lambda: User(name="guest"),注意别漏了冒号和括号 - 工厂函数里抛异常?dataclass 实例化时就会崩,没补救机会,得自己兜底
- 性能上,
lambda调用开销极小,不用顾虑;但频繁创建大对象(如读文件、连数据库)就不该放 default_factory 里
Pydantic v2 也用 default_factory,但行为略有不同
如果你在迁移到 Pydantic,要注意它对 default_factory 的处理更严格:不允许工厂函数返回 None(除非字段类型是 Optional),而 dataclass 不检查这个。另外 Pydantic 的工厂会在验证阶段多次调用,dataclass 只在 __init__ 时调一次。
所以别图省事复用同一段工厂代码跨框架使用,尤其是涉及副作用(比如计数器、日志打印)时,行为可能不一致。
最常被忽略的一点:default_factory 返回的值不会被 __post_init__ 自动识别为“用户未显式传入”,也就是说,即使字段靠工厂填了值,__post_init__ 里仍要按正常逻辑处理,不能假设它一定为空或未设置。










