Python collections.ChainMap 详解

collections.ChainMap 详解

ChainMap 是 Python 标准库 collections 模块中的一个类,Python 3.3+ 可用。它用于将多个映射(通常是字典)组合成一个单一的、可更新的视图。查找时按顺序从第一个映射到最后一个映射,写入时只作用于第一个映射。

python 复制代码
from collections import ChainMap

1. 核心思想

ChainMap 不合并字典,而是维护一个映射列表 maps,按优先级从高到低排列。

python 复制代码
defaults = {'theme': 'dark', 'language': 'en'}
user = {'theme': 'light'}
env = {'language': 'zh'}

config = ChainMap(env, user, defaults)

此时 config.maps 为:

bash 复制代码
[env, user, defaults]
# 即 [{'language': 'zh'}, {'theme': 'light'}, {'theme': 'dark', 'language': 'en'}]

查找顺序:envuserdefaults

写入顺序:只写入 env,即 maps[0]

2. 基本行为

2.1 查找

python 复制代码
print(config['theme'])    # light,来自 user
print(config['language']) # zh,来自 env
print(config['show'])     # KeyError

__getitem__ 会依次查找每个映射,返回第一个匹配的值。

2.2 写入

python 复制代码
config['theme'] = 'blue'
print(env)     # {'language': 'zh', 'theme': 'blue'}
print(user)    # {'theme': 'light'},不变
print(defaults)# {'theme': 'dark', 'language': 'en'},不变

写入只影响第一个映射 maps[0]

2.3 删除

python 复制代码
del config['language']
print(env)      # {'theme': 'blue'}
print(config['language'])  # en,回退到 defaults

删除也只作用于第一个映射。如果键不在第一个映射中,即使后面的映射有,也会抛出 KeyError

python 复制代码
try:
    del config['theme']  # theme 不在 env 中,但在 user/defaults 中
except KeyError as e:
    print('KeyError:', e)

2.4 动态性

ChainMap 是视图,不是副本。修改底层映射会立即反映到 ChainMap 上。

bash 复制代码
d1 = {'a': 1}
d2 = {'b': 2}
cm = ChainMap(d1, d2)

print(cm['a'])  # 1
d1['a'] = 100
print(cm['a'])  # 100
d2['c'] = 3
print(cm['c'])  # 3

3. 常用属性与方法

属性/方法 说明
maps 底层映射列表,按优先级从高到低排列。可修改。
new_child(m=None, **kwargs) 返回新的 ChainMap,在开头插入一个新映射。
parents 属性,返回新的 ChainMap,包含除第一个映射外的所有映射。
__getitem__ 按顺序查找,返回第一个匹配值。
__setitem__ 只写入 maps[0]
__delitem__ 只从 maps[0] 删除。
__contains__ 任意映射包含该键则返回 True
__iter__ 迭代所有唯一键。
__len__ 返回唯一键的数量。
__bool__ 任意映射非空则为 True
get(key, default=None) 查找所有映射,不存在返回默认值。
setdefault(key, default=None) 如果键存在任意映射,返回其值;否则写入 maps[0]
pop(key, default) 只从 maps[0] 弹出。
popitem() 只从 maps[0] 弹出项。
clear() 只清空 maps[0]
update(other) 只更新 maps[0]
copy() 浅拷贝,底层映射共享。

4. new_childparents

这两个方法常用于模拟作用域链。

python 复制代码
base = ChainMap({'a': 1}, {'b': 2})
child = base.new_child({'a': 10, 'c': 3})

print(child['a'])  # 10
print(child['b'])  # 2
print(child.maps)
# [{'a': 10, 'c': 3}, {'a': 1}, {'b': 2}]

print(child.parents.maps)
# [{'a': 1}, {'b': 2}]
  • new_child():创建一个新作用域,优先级最高。

  • parents:返回父作用域链,去掉当前最高优先级映射。

Python 3.10+ 中 new_child 还支持关键字参数:

python 复制代码
child = base.new_child(a=10, c=3)

5. 示例:配置分层

python 复制代码
from collections import ChainMap

defaults = {'theme': 'dark', 'language': 'en', 'show': True}
user = {'theme': 'light'}
env = {'language': 'zh'}

config = ChainMap(env, user, defaults)

print(config['theme'])    # light
print(config['language']) # zh
print(config['show'])     # True

config['theme'] = 'blue'  # 写入 env
print(env)                # {'language': 'zh', 'theme': 'blue'}
print(user)               # {'theme': 'light'}
print(config['theme'])    # blue

del config['language']    # 只从 env 删除
print(config['language']) # en,回退到 defaults

6. setdefaultpop 的特殊行为

setdefault

python 复制代码
cm = ChainMap({}, {'x': 1})
print(cm.setdefault('x', 100))  # 1,来自第二个映射
print(cm.maps[0])               # {},不会写入第一个映射

print(cm.setdefault('y', 200))  # 200
print(cm.maps[0])               # {'y': 200}

如果键存在于任何映射中,setdefault 不会写入第一个映射。

pop

bash 复制代码
cm = ChainMap({'x': 1}, {'x': 2, 'y': 3})
print(cm.pop('x'))  # 1,只从第一个映射弹出
print(cm['x'])      # 2,来自第二个映射

try:
    cm.pop('y')     # y 不在第一个映射中
except KeyError:
    print('KeyError')

poppopitemclearupdatedel 都只作用于第一个映射。


7. 与 dict 合并对比

bash 复制代码
merged = {**defaults, **user, **env}   # 静态副本,修改不会反映
chain = ChainMap(env, user, defaults)  # 动态视图,内存高效
  • dict 合并会复制所有键值。

  • ChainMap 只保存映射引用,查找时按顺序搜索。

  • ChainMap 写入只影响第一个映射。

  • ChainMap 的底层映射变化会动态反映。


8. 典型应用场景

  1. 配置管理:默认配置、用户配置、环境变量、命令行参数分层覆盖。

  2. 模拟作用域:局部作用域、全局作用域、内置作用域。

  3. 模板上下文 :如 Django 的 Context 类似机制。

  4. 只读叠加视图:需要按优先级查看多个字典,但不想合并复制。

  5. 临时覆盖:创建子上下文,不污染原始字典。


9. 注意事项

  1. 写入只影响第一个映射

    cm['x'] = 1 等价于 cm.maps[0]['x'] = 1

  2. 删除、pop、popitem、clear、update 只作用于第一个映射

    即使后面的映射有该键,也不会被删除或更新。

  3. 迭代顺序不保证与查找顺序一致

    list(ChainMap({'a': 1}, {'b': 2})) 可能得到 ['b', 'a']。重复键只出现一次。

  4. len 返回唯一键数量

    python 复制代码
    cm = ChainMap({'a': 1}, {'a': 2, 'b': 3})
    print(len(cm))  # 2
  5. in 检查所有映射

    python 复制代码
    cm = ChainMap({'a': 1}, {'b': 2})
    print('b' in cm)  # True
  6. 底层映射修改会影响 ChainMap

    因为它是视图,不是副本。

  7. 不是线程安全的

    多线程环境下需要额外同步。

  8. 可以子类化实现 __missing__

    python 复制代码
    class MyChainMap(ChainMap):
        def __missing__(self, key):
            return 'default'
    
    cm = MyChainMap({'a': 1})
    print(cm['b'])  # default
  9. copy() 是浅拷贝

    底层映射对象仍然共享。

  10. ChainMap 不支持 | 合并运算符

    Python 3.9+ 的 dict 支持 |,但 ChainMap 没有。


10. 总结

ChainMap 是一个轻量级的"多字典视图",适合分层查找、配置覆盖和模拟作用域。它不复制数据,查找按 maps 顺序进行,写入只作用于第一个映射。理解它的"视图"本质和"只写第一层"的规则,就能正确高效地使用它。

相关推荐
三十岁老牛再出发1 小时前
9月16日总结
python·深度学习·机器学习
计算机源码社1 小时前
基于大数据的全球温室气体排放燃料结构与碳强度评估研究-基于Spark的全球温室气体排放多维度检测与评估分析
大数据·hadoop·python·数据挖掘·数据分析·spark·毕业设计
eybk1 小时前
用Kivy制作手机相片分类局域网传送工具,还能传输数据库文件
开发语言·python
ctlover1 小时前
hot-100刷题笔记
数据结构·python
三岁就很~酷~1 小时前
ai开发 python+claudecode环境搭建
python·ai编程
赵钰老师1 小时前
基于ArcGIS Pro、R、INVEST等多技术融合下生态系统服务权衡与协同动态分析
python·arcgis·数据分析·r语言
言乐62 小时前
Python:用AES-GCM算法加密链接内容,并生成可分享的加密链接和解密工具
python·django·virtualenv·pygame·tornado
小静AI工程实验室2 小时前
Claude Code 实用教程:Windows、macOS、Linux 从安装到项目调试与验收
人工智能·python·开发工具·claude code
OKkankan2 小时前
Python常用容器与导入语法详解(二)
数据结构·python