Python dataclasses.field 默认工厂为什么不能直接写成列表
在 dataclasses 里给列表字段设置默认值时,最容易写错的是把 default_factory=list 写成 default_factory=[]。两者只差一对括号,含义却完全不同:前者传入“以后可以被调用的工厂”,后者传入“现在已经创建好的列表”。正确写法是:
from dataclasses import dataclass, field
@dataclass
class Basket:
# 传入 list 类型;每次构造 Basket 时由它创建一个新列表。
items: list[str] = field(default_factory=list)
default_factory要的是零参数可调用对象,list可以,[]不可以。- 可变容器应使用
field(default_factory=list)、dict或set,不要把实例写进default。 - 用两个实例检查内容和
is身份,能快速确认列表没有跨对象共享。
为什么 default_factory=[] 不是列表默认值
field() 的 default_factory 参数定义的是“需要默认值时调用的零参数函数”。list 本身是一个可调用类型,调用 list() 会得到新列表;[] 则已经是列表对象,不是函数。于是下面的写法把参数类型弄反了:
from dataclasses import dataclass, field
@dataclass
class WrongBasket:
# 错误:[] 是列表实例,不是可以被调用的零参数工厂。
items: list[str] = field(default_factory=[])
# 构造实例时,生成的 __init__ 会尝试执行 [](),因此会报不可调用错误。
另一种常见误写是 items: list[str] = []。这不是“每次构造自动复制一份列表”的语法。数据类仍然遵循普通 Python 类的默认值语义;当前文档还明确说明,不可哈希的默认对象会被拒绝,以拦住许多可变默认值错误。即使某个自定义可变对象没有立刻触发保护,也不应该把它当成实例级默认容器。

list 类型作为工厂,把 [] 作为实例,理解 default_factory 的参数边界。把列表类型交给工厂,而不是提前创建列表
修复时只需要把方括号移除。field(default_factory=list) 表示:这个字段没有调用方传入值时,初始化逻辑调用一次 list()。工厂不接收 self,也不应该依赖另一个字段的值。
from dataclasses import dataclass, field
@dataclass
class Basket:
# list 是零参数可调用对象;每个 Basket 都会获得独立容器。
items: list[str] = field(default_factory=list)
@dataclass
class Settings:
# 字典和集合也使用各自的类型作为工厂。
labels: dict[str, str] = field(default_factory=dict)
flags: set[str] = field(default_factory=set)
如果默认列表需要固定的初始内容,可以传入零参数的 lambda。它每次返回新对象,而不是把同一个列表挂到所有实例上:
from dataclasses import dataclass, field
@dataclass
class QueueConfig:
# 工厂不接收构造参数;返回值是每个实例自己的新列表。
stages: list[str] = field(default_factory=lambda: ["queued", "ready"])
不过,固定空列表优先写成 default_factory=list,可读性更好,也更直接地表达“调用列表类型创建容器”。
用两个实例指标确认列表彼此隔离
排查这类问题时,不要只看对象打印出来的内容。建议同时看三个指标:初始内容是否为空、向对象 A 追加元素后对象 B 是否仍为空、两个 items 是否不是同一对象。这个小检查能把“值看起来一样”和“身份真的独立”区分开。
first = Basket()
second = Basket()
# 先检查两个新实例都从空列表开始。
assert first.items == []
assert second.items == []
# 只修改对象 A;对象 B 不应被连带修改。
first.items.append("python")
assert first.items == ["python"]
assert second.items == []
# is 检查身份,确认不是同一个列表对象。
assert first.items is not second.items
可以把结果整理成一张速查表:
| 写法 | 传给字段的东西 | 适用判断 |
|---|---|---|
default_factory=list | 零参数可调用类型 | 空列表,推荐 |
default_factory=lambda: [...] | 零参数函数 | 每次创建带初始内容的新列表 |
default_factory=[] | 已创建的列表实例 | 实例化时不可调用,错误 |
default=[] | 共享候选对象 | 可变默认值风险,不应使用 |

items 的独立关系,检查 append 是否越过实例边界。有依赖关系时不要硬塞进零参数工厂
default_factory 适合“只要一个新对象”的初始化。如果列表内容必须根据构造参数决定,工厂拿不到 self,此时可以先给空列表,再在 __post_init__ 中补齐;或者直接编写类方法构造器,把依赖关系写在更明确的位置。
from dataclasses import dataclass, field
@dataclass
class Plan:
name: str
tasks: list[str] = field(default_factory=list)
def __post_init__(self) -> None:
# 只有调用方未提供任务时,才根据 name 生成实例自己的初始内容。
if not self.tasks:
self.tasks.append(f"prepare:{self.name}")
这里的边界是:工厂负责创建容器,__post_init__ 负责读取实例字段并决定内容。若调用方显式传入任务,应该再根据业务规则决定是否保留空列表判断,避免把“用户明确传入空列表”和“没有提供任务”混为一谈。
常见问题
default_factory=list 和 default_factory=lambda: [] 有区别吗?
对空列表这个场景,结果都能做到每个实例新建列表;list 更短、更直接。需要固定初始元素或更复杂的无参创建逻辑时,才使用 lambda 或命名工厂。
为什么元组可以直接写成 default=()?
元组通常作为不可变默认值使用,多个实例共享同一个不可变对象不会产生 append 一类的状态串改。只要容器内容需要修改,就回到对应的 default_factory。
工厂可以写成带参数的函数吗?
不能直接这样写。数据类会以零参数调用工厂;需要外部参数时,用闭包捕获固定值、在 __post_init__ 中初始化,或提供显式构造方法。
记住一句话:default 放值,default_factory 放“如何创建值”的可调用对象。对列表、字典和集合,优先把类型本身交给工厂,再用两个实例的内容与 is 检查确认隔离。
Go embed.FS 通过 fs.ValidPath 校验资源名时要注意什么
- 上一篇
- Go embed.FS 通过 fs.ValidPath 校验资源名时要注意什么
- 下一篇
- Go embed.FS 读取不存在资源时怎么区分错误类型
-
- 文章 · python教程 | 44分钟前 | 打包 · python · C扩展 · free-threading · wheel Python 3.15 abi3t cp315t
- Python 3.15 的 abi3t 与 cp315t wheel 如何选择
- 331浏览 收藏
-
- 文章 · python教程 | 7小时前 | python · typing · Annotated · 运行时反射 typing.Annotated Python类型注解
- Python typing.Annotated 的元数据怎么在运行时读取
- 347浏览 收藏
-
- 文章 · python教程 | 14小时前 | 并发 · 日志 · python · Python logging QueueHandler QueueListener
- Python logging QueueListener 停止时怎么保证剩余日志写完
- 496浏览 收藏
-
- 文章 · python教程 | 23小时前 |
- Python asyncio.wait_for 超时后如何保留任务清理机会
- 469浏览 收藏
-
- 文章 · python教程 | 1天前 |
- Python asyncio.timeout 和 wait_for 的超时范围怎么选择
- 386浏览 收藏
-
- 文章 · python教程 | 1天前 | Windows · 跨平台 · Python教程 · 文件系统 · Python Python 3.15 os.path.isreserved Windows 保留路径 ntpath
- Python 3.15 os.path.isreserved 怎么判断 Windows 保留路径
- 343浏览 收藏
-
- 文章 · python教程 | 1天前 | Python教程 · pathlib · 文件系统 · 版本兼容 · Python 目录权限 Python 3.15 pathlib.Path.mkdir parent_mode
- Python pathlib.Path.mkdir 的 parent_mode 怎么影响中间目录
- 243浏览 收藏
-
- 文章 · python教程 | 1天前 | 并发 · python · C API · Python C扩展 free-threaded Py_GIL_DISABLED
- Python free-threaded 构建中 C 扩展如何声明线程安全状态
- 260浏览 收藏
-
- 文章 · python教程 | 1天前 | python · risc-v · Python 3.15 · riscv64 · 原生扩展 · Python打包 · RISC-V wheel Python 3.15 riscv64 Python扩展
- Python 3.15 RISC-V 支持落地后扩展构建要检查哪些假设
- 493浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 61次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 217次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 145次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 79次使用
-
- Generrated
- Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
- 56次使用
-
- Go语言设置JSON的默认值操作
- 2022-12-28 127浏览
-
- 解决goxorm无法更新值为默认值的问题
- 2022-12-30 157浏览
-
- mysql时间字段默认设置为当前时间实例代码
- 2023-01-01 288浏览
-
- MySQL 字段默认值该如何设置
- 2023-01-07 220浏览

