Python 3.15 frozendict 内置类型的配置使用场景
我第一次把 Python 3.15 的 frozendict 放进配置层,是因为一个“默认值不该被业务函数悄悄改掉”的小问题。过去我会在模块里放一个普通 dict,再靠约定不去修改它;配置一多,这个约定很快就变成了隐形依赖。Python 3.15 将 frozendict 加入内置类型后,默认配置、只读快照和可哈希的缓存参数终于有了更清晰的表达方式。
我的结论是:如果配置在创建后只读、需要跨模块共享,或者希望作为缓存键的一部分,frozendict 很合适;如果数据还要频繁原地更新,继续用 dict;如果只想把一个已有字典临时暴露成只读视图,MappingProxyType 往往更直接。关键不在于把所有字典都“冻结”,而在于先确定所有权、更新频率和版本门槛。
我先把配置生命周期拆成三种负载
配置场景看起来相似,实际有三种不同负载。第一种是进程启动时生成的默认配置,例如重试次数、超时时间和功能开关;它们在创建后不应被某个调用方原地覆盖。第二种是启动参数或环境变量带来的覆盖值,通常需要在初始化阶段合并一次。第三种是运行期间不断变化的动态配置,它需要明确的发布与替换机制,不能靠对一个共享对象做原地修改。
我会把前两种收敛为一个配置快照:外部输入仍然可以先落在普通 dict,但交给业务模块前转换为 frozendict。这样下游拿到的是一个有明确边界的映射;动态配置则生成新快照,再把引用整体替换。这个选择比“看到字典就冻结”更稳,因为它把可变阶段和只读阶段分开了。

先掌握 frozendict 的构造和读取方式
frozendict 是 Python 3.15 的内置类型,不需要安装第三方包。它接受关键字参数、一个映射或键值对迭代器,也可以同时接收位置参数和关键字参数。和普通字典一样,它保留插入顺序;但它不是 dict 的子类,类型判断和泛型标注要单独处理。
from collections.abc import Mapping
# 关键字参数适合写少量固定默认值
DEFAULTS = frozendict(timeout=3, retries=2, region="cn-shanghai")
# 先在可变阶段合并外部输入,再把结果交给只读边界
raw_config = {**DEFAULTS, "retries": 4}
CONFIG = frozendict(raw_config)
# 读取方式与字典接近;这里不对配置对象做原地修改
def get_timeout(config: Mapping[str, object]) -> int:
# 用 Mapping 接收 dict 和 frozendict,减少不必要的类型耦合
return int(config["timeout"])
print(CONFIG["retries"])
print(list(CONFIG.keys()))
如果项目需要兼容旧 Python 版本,不能只把构造器写进代码就结束:旧版本没有这个内置名字,导入或执行路径都要有清晰的版本门槛。对于只读函数参数,我更倾向于标注 Mapping[str, object],让调用方可以传入 dict、frozendict 或其他映射实现。
按所有权和哈希需求比较三种映射
我在架构评审中会用两个问题做选择:这个对象的底层数据还需要被原地更新吗?它是否需要成为另一个映射的键、集合元素或缓存函数参数?答案通常比“哪个类型更现代”更有用。
| 类型 | 适合的边界 | 要留意的地方 |
|---|---|---|
dict | 初始化、批量更新、组装请求参数 | 调用方可以修改,所有权必须靠约定或封装维护 |
MappingProxyType | 把已有字典临时暴露为只读视图 | 底层字典改变时视图也会看到变化,本身通常不能作为哈希键 |
frozendict | 创建后共享的配置快照、不可变参数、可哈希映射 | 是浅不可变对象,且只有键和值都可哈希时才能调用 hash() |
这里有一个容易忽略的差别:MappingProxyType 是对原字典的视图,适合“不允许这个消费者修改,但所有者仍会维护”的关系;frozendict 会把当前映射浅复制成一个独立快照,适合“这一版配置已经发布,后续更新要生成下一版”的关系。两种只读语义不一样,不能互换。

用并集运算生成新的配置快照
配置覆盖最适合采用“输入可变、输出不可变”的模式。先把命令行参数、环境变量或配置文件解析成普通映射,再用 | 生成新对象。右侧值覆盖左侧同名键,旧快照保持不变;这使得灰度配置、租户配置或请求级覆盖可以拥有清晰的生命周期。
# 默认值在模块加载后保持不变,作为基线快照
BASE = frozendict(timeout=3, retries=2, feature_x=False)
def build_snapshot(env_values: Mapping[str, object]) -> frozendict[str, object]:
# | 返回新的 frozendict,右侧覆盖同名配置,不改动 BASE
return BASE | frozendict(env_values)
staging = build_snapshot({"feature_x": True, "retries": 4})
production = build_snapshot({"timeout": 5})
# 两个环境各自拥有独立快照,后续替换引用即可发布新版本
print(staging["feature_x"])
print(production["timeout"])
这里不要把 |= 误解成对原对象的原地写入。对 frozendict 来说,增强赋值会让变量重新绑定到一个新对象,旧变量引用的快照仍然不变。这个特性很适合把“发布新配置”写成一次引用替换,但并不自动解决多线程中的业务一致性;配置发布仍要由应用自己的生命周期管理。
哈希和浅不可变是两条必须写进设计说明的风险
当我想把配置快照放进缓存键时,会先确认所有键和值都可哈希。字符串、整数、布尔值、元组等常见值通常没有问题,但列表、集合和普通字典不能参与哈希。构造成功不等于一定可哈希,真正调用 hash() 时才会暴露这个边界。
# 只有键和值都可哈希时,快照才适合作为另一个映射的键
CACHEABLE = frozendict(region="cn-shanghai", retries=2)
cache = {CACHEABLE: "compiled-result"}
# 这个对象可以创建,但其中的列表让整体不能哈希
NOT_CACHEABLE = frozendict(tags=["blue", "canary"])
# 嵌套容器仍可能被外部修改,frozendict 只冻结外层映射
mutable_value = ["initial"]
snapshot = frozendict(tags=mutable_value)
mutable_value.append("later")
print(snapshot["tags"])
如果目标是缓存键,我会把嵌套列表改成元组,把嵌套字典也转换成递归的不可变结构;如果目标只是防止调用方增删顶层配置,则不必为了追求“深冻结”而引入额外复杂度。官方定义强调的是浅不可变:外层键值关系不会被 __setitem__ 改写,但值对象本身的可变性仍由它自己的类型决定。
落地时别忘了 dict 判断和版本门槛
frozendict 不是 dict 子类,所以旧代码里的 isinstance(value, dict) 可能会把它排除。只要函数真正需要的是映射能力,最好改成 isinstance(value, Mapping);如果调用方确实只接受内置可变字典,再显式写出支持的类型集合,并说明后续是否会复制。
from collections.abc import Mapping
def normalize_options(value: object) -> frozendict[str, object]:
# 只依赖映射协议,避免把 frozendict 错误当成非法输入
if not isinstance(value, Mapping):
raise TypeError("options must be a mapping")
# 复制到新的 frozendict,隔离调用方后续对原 dict 的修改
return frozendict(value)
def accepts_only_builtin_maps(value: object) -> bool:
# 只有确实需要 dict 语义时才使用这个更窄的判断
return isinstance(value, (dict, frozendict))
版本方面,Python 3.15 的内置文档和 PEP 814 是这项能力的事实边界。项目如果仍需运行在 3.14 或更早版本,可以继续使用第三方不可变映射或 MappingProxyType,但不要在兼容层里把它们误称为同一个类型;更稳妥的做法是把“生成只读配置”的职责藏在一个小工厂里,升级时只替换这一处。
我的配置落地清单
- 先画出配置生命周期:哪些值只在启动时组装,哪些值会在运行期间变化。
- 把可变的解析阶段和只读的消费阶段分开,发布后使用新的
frozendict快照。 - 需要临时只读视图时考虑
MappingProxyType,需要独立快照或哈希能力时考虑frozendict。 - 把嵌套列表、集合和字典单独列入风险清单,不把外层不可变误写成深不可变。
- 把
dict类型判断改成映射协议判断,除非业务真的依赖可变字典的方法。 - 在 CI 中把 Python 3.15 作为使用内置
frozendict的最低版本,并为旧版本准备替代实现。
如果你的应用只是接收一段马上要更新的临时参数,dict 仍然是最简单的选择;如果是把一份已经发布的配置交给多个模块共享,frozendict 才能把这份“不可原地修改”的意图写进类型本身。对我来说,真正的收益不是多了一个容器,而是配置快照的所有权终于有了可读的边界。
常见问题
frozendict 能完全替代 dict 吗?
不能。它省略了更新、删除和弹出等原地修改方法,更适合只读阶段;组装数据时仍使用普通 dict 往往更自然。
frozendict 一定可以作为缓存键吗?
不一定。只有键和值都可哈希时,整体才可哈希;包含列表或普通字典的快照仍然不能直接调用 hash()。
为什么不用 MappingProxyType?
如果你需要的是某个已有字典的实时只读视图,MappingProxyType 很合适;如果你需要独立快照、并集生成新版本或缓存键,再考虑 frozendict。
参考资料:https://www.python.org/downloads/release/python-3150/、https://docs.python.org/3.15/library/stdtypes.html#mapping-types-dict-frozendict、https://peps.python.org/pep-0814/
net/http 客户端关闭连接后请求体重用的限制
- 上一篇
- net/http 客户端关闭连接后请求体重用的限制
- 下一篇
- embed.FS 与 fs.Sub 组合静态资源服务
-
- 文章 · python教程 | 19分钟前 |
- Python logging Formatter 统一结构化字段输出
- 185浏览 收藏
-
- 文章 · python教程 | 4小时前 | 序列化 · python · Python pickle 进程池 multiprocessing Pool
- Python multiprocessing 进程池传递不可序列化对象
- 255浏览 收藏
-
- 文章 · python教程 | 5小时前 | 数据一致性 · Python教程 · Python 事务 自动提交 sqlite3 autocommit isolation_level
- Python sqlite3 事务模式与自动提交边界
- 197浏览 收藏
-
- 文章 · python教程 | 6小时前 |
- Python contextlib.nullcontext 统一同步异步入口
- 316浏览 收藏
-
- 文章 · python教程 | 6小时前 | 面向对象 · python · Python教程 · InitVar __post_init__ Python dataclass 派生字段 field(init=False)
- Python dataclass __post_init__ 计算派生字段
- 303浏览 收藏
-
- 文章 · python教程 | 7小时前 |
- Python typing.TypeGuard 处理复杂容器类型收窄
- 437浏览 收藏
-
- 文章 · python教程 | 9小时前 |
- Python os.fspath 支持自定义路径对象
- 214浏览 收藏
-
- 文章 · python教程 | 11小时前 | 异常处理 · 异步编程 · Python教程 · asyncio · 后台任务 任务取消 CancelledError Python asyncio asyncio.shield
- Python asyncio.shield 保护后台任务免受外层取消
- 407浏览 收藏
-
- 文章 · python教程 | 13小时前 |
- Python configparser ExtendedInterpolation 组织分层配置
- 480浏览 收藏
-
- 文章 · python教程 | 1天前 | python · Python tomllib TOMLDecodeError
- Python tomllib 解析失败时如何定位具体键与行列
- 198浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 408次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 487次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 494次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 443次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 271次使用
-
- 总结Golang四种不同的参数配置方式
- 2023-01-07 477浏览
-
- Go语言简介和环境配置
- 2023-01-07 109浏览
-
- Go微服务项目配置文件的定义和读取示例详解
- 2023-01-08 298浏览
-
- Goland的设置与配置全过程
- 2023-02-25 192浏览
-
- zap接收gin框架默认的日志并配置日志归档示例
- 2023-01-07 335浏览

