PYTHON 3.15 · PEP 814
Python 3.15 终于要有内置的 不可变字典 了。
PEP 814 刚刚通过,frozendict 将在 Python 3.15 成为内置类型。不需要 import,不需要第三方库,直接用。但这件事的意义远不止"多了一个类型"——它可能是 Python 向"默认不可变"迈出的试探性一步。
一句话:创建后不能修改的字典。键值对一旦写入,就再也改不了、删不掉、加不进。
听起来很简单,对吧?但 Python 等了 30 多年才把它做进内置。
目前你想用不可变字典,要么用 tuple 嵌套键值对(可读性差),要么用 types.MappingProxyType(名字就劝退),要么装第三方的 frozendict 库。没有一个方案让人满意。

注意两个关键特性:可哈希意味着它可以作为另一个字典的 key 或集合的元素;联合操作符让你像合并普通字典一样合并 frozendict。这两个特性组合起来,打开了函数式编程风格的大门。
最常见的问题:用 tuple 存键值对不就行了吗?技术上可以,但语义不对。
tuple 传达的信息是"一组有序的值",frozendict 传达的是"一组命名的映射"。当你用 tuple 存配置,你数位置才能知道哪个是 host、哪个是 port;用 frozendict,key 就是最好的注释。
这不是强迫症,是工程实践。代码被阅读的次数远多于被编写的次数。当一个数据结构能在声明时就表达清楚意图,省下的不是几秒钟的阅读时间,而是减少理解成本带来的 bug。
更实际的价值在缓存和配置场景。函数参数缓存(functools.lru_cache)要求参数可哈希——用 dict 不行,用 tuple 又丢失语义。frozendict 完美解决这个问题:既可哈希,又保留键值对的语义。
核心价值:frozendict 不是为了替代 tuple,而是在"命名键值对 + 不可变"的场景下,提供语义正确的表达方式。
PEP 814 提案中有一个容易被忽略的细节:dict.freeze() 方法也在讨论中。
如果 dict.freeze() 通过,你将可以用 d.freeze() 把任何普通字典变成不可变的——就像 str 和 bytes 的关系一样:一个可变,一个不可变,随时转换。
这意味着什么?看看 Python 的历史:str 曾经是可变的(Python 1.x),后来变成了不可变;list 是可变的,tuple 一直不可变。现在 dict 是可变的,frozendict 补上了最后一块拼图。
再往深处想:如果未来有一天,dict 字面量默认创建的就是 frozendict,需要显式标记才能得到可变字典呢?听起来疯狂,但 Go 的 map 就是引用类型(天然不可变),Rust 的 HashMap 默认不可变——"默认不可变"正在成为现代语言的设计趋势。
但先别激动。Python 不太可能走那么远。Guido 多次强调 Python 的设计哲学是"实用优于纯粹",强制不可变不符合这个哲学。更现实的预期是:frozendict 作为可选工具存在,鼓励你在需要不可变性的场景主动使用。
不过信号本身值得关注:Python 核心团队开始认真对待不可变性了。30 年来第一次,不可变字典从"第三方的妥协方案"变成了"语言的一部分"。
LAST WORD
frozendict 入内置,不只是多了一个类型——它是 Python 在 30 年后对"不可变性"的一次正式回应。
Python 3.15 还在开发中,最终 API 可能调整。但方向已经明确了。如果你现在的代码里有用 tuple 伪装字典、用 MappingProxyType 包裹配置、或者装了第三方 frozendict 库的场景,可以开始准备迁移了。
一个简单的自查:你的代码里有多少 dict 创建后就没再修改过?如果比例超过 30%,frozendict 3.15 正式发布后值得认真考虑。
多一个内置类型不重要。重要的是,Python 终于承认:不是所有数据都应该是可变的。