0

0

使用 collections.ChainMap 实现深度字典合并

霞舞

霞舞

发布时间:2025-08-11 19:10:19

|

379人浏览过

|

来源于php中文网

原创

使用 collections.chainmap 实现深度字典合并

本文探讨了如何利用 Python 的 collections.ChainMap 实现深度字典合并。标准 ChainMap 仅提供浅层合并,即遇到重复键时只取第一个值。针对嵌套字典场景,我们通过自定义 DeepChainMap 类并重写其 __getitem__ 方法,使其能够递归地合并相同键下的字典值,从而实现复杂的深度合并逻辑,有效处理多层嵌套的数据结构。

理解 collections.ChainMap 及其局限性

collections.ChainMap 是 Python collections 模块提供的一个类,它将多个字典或映射组合成一个单一的可更新视图。它维护一个映射列表,并在查找键时按照列表的顺序从左到右进行搜索,返回第一个找到的值。这对于需要按优先级合并配置或查找变量的场景非常有用。

考虑以下两个字典 a 和 b:

a = {'123': {'player': 1, 'opponent': 2},
     '18': {'player': 10, 'opponent': 12}}

b = {'123': {'winner': 1},
     '180': {'winner': 2}}

如果使用标准的 ChainMap 进行合并:

from collections import ChainMap

merged_map = ChainMap(a, b)
print(dict(merged_map))

输出结果为:

{'123': {'player': 1, 'opponent': 2}, '180': {'winner': 2}, '18': {'player': 10, 'opponent': 12}}

可以看到,对于键 '123',ChainMap 仅仅返回了字典 a 中对应的值 {'player': 1, 'opponent': 2},而忽略了字典 b 中 '123' 键下的 {'winner': 1}。这是因为 ChainMap 实现了浅层合并,它在遇到重复键时,只会返回映射列表中第一个找到的键值对,不会进一步合并键对应的值(如果它们也是字典)。

然而,我们期望的输出是:

{'123': {'player': 1, 'opponent': 2, 'winner': 1},
 '18': {'player': 10, 'opponent': 12},
 '180': {'winner': 2}}

这要求当多个字典中存在相同的键,并且这些键对应的值本身也是字典时,我们需要将这些嵌套的字典进行递归合并,而非简单地取第一个。

实现自定义 DeepChainMap 进行深度合并

为了实现这种深度合并行为,我们需要创建一个 ChainMap 的子类,并重写其 __getitem__ 方法。__getitem__ 是 Python 对象在通过 obj[key] 访问元素时调用的特殊方法。通过自定义此方法,我们可以在键查找时引入递归合并逻辑。

白月生产企业订单管理系统GBK2.0  Build 080807
白月生产企业订单管理系统GBK2.0 Build 080807

请注意以下说明:1、本程序允许任何人免费使用。2、本程序采用PHP+MYSQL架构编写。并且经过ZEND加密,所以运行环境需要有ZEND引擎支持。3、需要售后服务的,请与本作者联系,联系方式见下方。4、本程序还可以与您的网站想整合,可以实现用户在线服务功能,可以让客户管理自己的信息,可以查询自己的订单状况。以及返点信息等相关客户利益的信息。这个功能可提高客户的向心度。安装方法:1、解压本系统,放在

下载

以下是 DeepChainMap 的实现:

from collections import ChainMap

class DeepChainMap(ChainMap):
    """
    ChainMap 的变体,支持对嵌套字典进行递归深度合并。
    """
    def __getitem__(self, key):
        """
        重写 __getitem__ 方法,实现深度合并逻辑。
        当查找到的第一个值是字典时,递归地将所有映射中
        该键对应的字典值合并为一个新的 DeepChainMap。
        """
        # 收集所有映射中与给定键关联的值
        # 注意:这里会收集所有找到的值,即使它们不是字典
        values = (mapping[key] for mapping in self.maps if key in mapping)

        try:
            # 获取第一个找到的值
            first = next(values)
        except StopIteration:
            # 如果在任何映射中都未找到该键,则抛出 KeyError
            return self.__missing__(key)

        # 如果第一个找到的值是字典类型,则进行递归合并
        if isinstance(first, dict):
            # 使用当前 DeepChainMap 类,将第一个字典和后续找到的所有字典
            # 作为新的映射列表,递归创建并返回一个新的 DeepChainMap 实例。
            # 这样就实现了对嵌套字典的深度合并。
            return self.__class__(first, *values)

        # 如果第一个找到的值不是字典,则直接返回该值(标准 ChainMap 行为)
        return first

    def __repr__(self):
        """
        重写 __repr__ 方法,使其在打印时更清晰,直接显示为字典形式。
        """
        return repr(dict(self))

代码解析:

  1. __getitem__(self, key) 方法:
    • values = (mapping[key] for mapping in self.maps if key in mapping): 这一行是关键。它不是简单地返回第一个找到的值,而是创建一个生成器表达式,迭代 self.maps(即 ChainMap 内部维护的字典列表),收集所有映射中与 key 关联的值。
    • first = next(values): 尝试从 values 生成器中获取第一个值。
    • except StopIteration: return self.__missing__(key): 如果 values 为空(即 key 在所有映射中都不存在),则调用 __missing__ 方法(通常会抛出 KeyError),保持与标准字典和 ChainMap 一致的错误行为。
    • if isinstance(first, dict): return self.__class__(first, *values): 这是实现深度合并的核心逻辑。如果 first 值是一个字典,这意味着我们需要进一步合并它。我们通过 self.__class__(first, *values) 递归地创建了一个新的 DeepChainMap 实例。这个新的 DeepChainMap 的映射列表由 first 字典和 values 生成器中剩余的所有字典组成。当访问这个新的 DeepChainMap 内部的键时,会再次触发 __getitem__ 方法,从而实现多层嵌套字典的深度合并。
    • return first: 如果 first 值不是字典(例如,是字符串、整数、列表等),则直接返回它,保持 ChainMap 的默认行为。
  2. __repr__(self) 方法:
    • return repr(dict(self)): 这个方法是为了让 DeepChainMap 实例在被 print() 或在交互式环境中显示时,能够以更直观的字典形式展现,方便调试和理解。

示例用法

现在,我们可以使用 DeepChainMap 来实现我们期望的深度合并:

# 示例字典数据
a = {'123': {'player': 1,
             'opponent': 2},
     '18': {'player': 10,
            'opponent': 12}
    }

b = {'123': {'winner': 1},
     '180': {'winner': 2}
    }

# 使用自定义的 DeepChainMap 进行合并
deep_merged_map = DeepChainMap(a, b)

# 打印合并后的结果
print(deep_merged_map)

输出结果:

{'123': {'winner': 1, 'player': 1, 'opponent': 2}, '180': {'winner': 2}, '18': {'player': 10, 'opponent': 12}}

可以看到,对于键 '123',a 和 b 中对应的嵌套字典 {'player': 1, 'opponent': 2} 和 {'winner': 1} 已经被成功地合并成 {'winner': 1, 'player': 1, 'opponent': 2}。同时,其他不冲突的键 '18' 和 '180' 也被正确地包含在最终结果中。

注意事项与总结

  1. 合并顺序与优先级: DeepChainMap 继承了 ChainMap 的特性,其合并顺序仍然是从左到右。这意味着在深度合并嵌套字典时,如果多个字典中存在相同的内层键,靠前的字典中的值会优先被保留或合并。在上述示例中,{'winner': 1} 来自 b,{'player': 1, 'opponent': 2} 来自 a。最终 'winner' 键在 'player' 和 'opponent' 之后出现,这是因为在 DeepChainMap(a, b) 中,a 字典中的 '123' 被作为第一个映射传递给内层递归的 DeepChainMap。
  2. 非字典值的处理: 本文提供的 DeepChainMap 实现只针对值为字典的情况进行深度合并。如果相同键对应的值不是字典(例如,都是整数、字符串或列表),它将遵循 ChainMap 的默认行为,即返回第一个找到的值。如果需要对其他类型(如列表)进行特殊的深度合并(例如,列表拼接),则需要进一步扩展 __getitem__ 中的逻辑。
  3. 写入操作: 本文的 DeepChainMap 主要侧重于读取(__getitem__)时的深度合并行为。ChainMap 默认的 __setitem__ 和 __delitem__ 操作只作用于其映射列表中的第一个可写字典。如果需要对深度合并后的结构进行写入或删除操作,并希望这些操作能够影响到原始的嵌套字典,则需要额外重写 __setitem__ 和 __delitem__ 方法,并实现相应的递归写入/删除逻辑。
  4. 性能考量: 每次 __getitem__ 访问嵌套字典时都可能创建新的 DeepChainMap 实例,这在非常深层或频繁访问的场景下可能会有轻微的性能开销。但在大多数常规应用中,这种开销通常可以接受。

通过自定义 ChainMap 的 __getitem__ 方法,我们成功地将一个只支持浅层合并的工具扩展为能够处理复杂嵌套字典的深度合并器。这展示了 Python 灵活的面向对象特性和特殊方法(dunder methods)在构建自定义数据结构时的强大能力。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
python中print函数的用法
python中print函数的用法

python中print函数的语法是“print(value1, value2, ..., sep=' ', end=' ', file=sys.stdout, flush=False)”。本专题为大家提供print相关的文章、下载、课程内容,供大家免费下载体验。

186

2023.09.27

if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

776

2023.08.22

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

56

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

52

2025.11.27

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

56

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

52

2025.11.27

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

31

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.3万人学习

Django 教程
Django 教程

共28课时 | 3.6万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号