Python 进阶指南:如何优雅地合并两个字典列表

在处理实际的数据处理任务时,我们经常会遇到这样的情况:手头有两个包含字典的列表,每个字典代表一个实体的信息,而我们的目标是基于某个共同的“键”(比如 ID)将这两个列表合并。在今天的文章中,我们将深入探讨在 Python 中实现这一目标的多种方法。我们将从基本的方法入手,逐步深入到更高效、更 Pythonic(Python 风格)的实现方式,并分析它们的性能差异和适用场景。准备好了吗?让我们开始吧。

场景设定与问题陈述

假设我们正在处理一个学校的学生信息系统。我们有两个数据源:

  • Input1: 包含第一批学生的 INLINECODEe2087a9e(学号)和 INLINECODE9d474aaa(学校 ID)。
  • Input2: 包含第二批学生的 INLINECODEc7b1b5b7 和 INLINECODEfd9f99b1。

我们的目标是生成一个合并后的列表,其中具有相同 INLINECODE01d458e1 的字典会被合并在一起,且 INLINECODE5952a381 列表会被整合。

输入示例:

Input1 = [{‘roll_no‘: [‘123445‘, ‘1212‘], ‘school_id‘: 1},
          {‘roll_no‘: [‘HA-4848231‘], ‘school_id‘: 2}]

Input2 = [{‘roll_no‘: [‘473427‘], ‘school_id‘: 2},
          {‘roll_no‘: [‘092112‘], ‘school_id‘: 5}]

期望输出:

[{‘school_id‘: 1, ‘roll_no‘: [‘123445‘, ‘1212‘]}, 
 {‘school_id‘: 2, ‘roll_no‘: [‘HA-4848231‘, ‘473427‘]}, 
 {‘school_id‘: 5, ‘roll_no‘: [‘092112‘]}]

你可能会注意到,INLINECODE1775cc1b 为 2 的记录在两个列表中都存在,我们需要将它们的 INLINECODE0fcd3bc9 合并在一起。让我们来看看有哪些方法可以实现这一点。

方法一:使用 INLINECODEd857c6cf 和 INLINECODE0b428b5c 方法

这是我们最推荐的方法之一,因为它利用了 Python 标准库中的高效数据结构。INLINECODE17dc45f5 是 INLINECODEb84005ae 的一个子类,它会在访问不存在的键时自动创建一个默认值。这在这里非常有用,因为我们不需要手动检查键是否存在。

这种方法的核心思想是:

  • 创建一个 INLINECODE4df6b7db 来按 INLINECODE4fee2152 收集所有的学号。
  • 遍历两个列表,将学号 extend 到对应的学校 ID 下。
  • 最后将结果转换回字典列表。

代码示例

from collections import defaultdict

# 初始化列表数据
Input1 = [{‘roll_no‘: [‘123445‘, ‘1212‘], ‘school_id‘: 1},
          {‘roll_no‘: [‘HA-4848231‘], ‘school_id‘: 2}]

Input2 = [{‘roll_no‘: [‘473427‘], ‘school_id‘: 2},
          {‘roll_no‘: [‘092112‘], ‘school_id‘: 5}]

# 使用 defaultdict 来自动处理键的初始化
temp = defaultdict(list) 

# 遍历 Input1,将 roll_no 添加到对应 school_id 的列表中
for elem in Input1:
    # 使用 extend 方法将列表中的元素添加进去
    temp[elem[‘school_id‘]].extend(elem[‘roll_no‘])

# 遍历 Input2,执行同样的操作
for elem in Input2:
    temp[elem[‘school_id‘]].extend(elem[‘roll_no‘])

# 使用列表推导式将字典重新构造回我们需要的格式
Output = [{"roll_no": y, "school_id": x} for x, y in temp.items()]

# 打印最终结果
print("使用 defaultdict 合并后的结果:")
print(Output)

输出结果

使用 defaultdict 合并后的结果:
[{‘school_id‘: 1, ‘roll_no‘: [‘123445‘, ‘1212‘]}, {‘school_id‘: 2, ‘roll_no‘: [‘HA-4848231‘, ‘473427‘]}, {‘school_id‘: 5, ‘roll_no‘: [‘092112‘]}]

技术洞察

为什么我们要推荐这种方法?因为它的时间复杂度通常是 O(N)(假设字典的访问是平均 O(1) 的)。INLINECODE0cf2862d 帮我们省去了很多繁琐的 INLINECODEc49f1398 的判断语句,让代码更加简洁、可读。此外,INLINECODE59a90a95 方法直接操作列表内存,比使用 INLINECODEe7cf8f8c 运算符(创建新列表)更节省内存。

2026视角:从代码实现到智能维护

虽然上面的代码完美解决了问题,但在2026年的现代开发环境中,我们不仅仅是写代码,更是在维护“知识资产”。在最近的一个企业级数据清洗项目中,我们意识到这类逻辑往往隐藏着性能隐患。

AI辅助编码与“氛围编程” (Vibe Coding)

在编写上述逻辑时,我们现在的团队会大量使用 CursorWindsurf 这样的 AI IDE。作为经验丰富的开发者,我们不再通过死记硬背 API 来工作,而是通过“意图描述”来生成代码。

  • 对话式编程: 我们可能会直接在编辑器中输入注释:# Merge two lists of dicts based on school_id efficiently using collections。AI 不仅能补全代码,还能建议更优化的数据结构。

上下文感知: AI 现在能理解整个项目的结构。当我们修改这个合并逻辑时,AI 会自动提醒我们:“嘿,你在 INLINECODE3caacdff 里定义的 INLINECODEb3911a17 类似乎与这个结构重复,是否需要统一?”*

这种“氛围编程”让我们专注于业务逻辑(即“合并学生数据”),而让 AI 处理底层的语法优化和库引用。

方法二:构建中间字典进行合并(企业级工程实践)

有时候,为了代码的清晰度和可维护性,我们可以显式地构建一个中间字典。这种方法与 defaultdict 类似,但我们可以更清楚地看到每一步的逻辑操作,比如数据的更新和排序。

代码示例

# 初始化列表数据
Input1 = [{‘roll_no‘: [‘123445‘, ‘1212‘], ‘school_id‘: 1},
          {‘roll_no‘: [‘HA-4848231‘], ‘school_id‘: 2}]
Input2 = [{‘roll_no‘: [‘473427‘], ‘school_id‘: 2},
          {‘roll_no‘: [‘092112‘], ‘school_id‘: 5}]

# 初始化一个空字典来存储合并后的结果
# 这样做可以利用字典的哈希特性,实现快速查找
merged_dict = {}

# 遍历 Input1,填充初始数据
for dict1 in Input1:
    merged_dict[dict1[‘school_id‘]] = {
        ‘school_id‘: dict1[‘school_id‘], 
        ‘roll_no‘: dict1[‘roll_no‘]
    }

# 遍历 Input2,更新 merged_dict
for dict2 in Input2:
    # 检查 school_id 是否已经存在于合并字典中
    if dict2[‘school_id‘] in merged_dict:
        # 如果存在,合并 roll_no 列表
        merged_dict[dict2[‘school_id‘]][‘roll_no‘].extend(dict2[‘roll_no‘])
    else:
        # 如果不存在,添加一个新的条目
        merged_dict[dict2[‘school_id‘]] = {
            ‘school_id‘: dict2[‘school_id‘], 
            ‘roll_no‘: dict2[‘roll_no‘]
        }

# 将字典的值转换为列表,并根据 school_id 进行排序
# 这里的排序是为了保持输出结果的有序性,如果不需要顺序可以省略
merged_list = sorted(merged_dict.values(), key=lambda x: x[‘school_id‘])

# 打印结果
print("使用中间字典合并后的结果:")
print(merged_list)

输出结果

使用中间字典合并后的结果:
[{‘school_id‘: 1, ‘roll_no‘: [‘123445‘, ‘1212‘]}, {‘school_id‘: 2, ‘roll_no‘: [‘HA-4848231‘, ‘473427‘]}, {‘school_id‘: 5, ‘roll_no‘: [‘092112‘]}]

深入解析:类型提示与健壮性

在2026年的代码规范中,我们强烈建议为这种纯数据逻辑添加 Type Hinting(类型提示)。这不仅有助于 IDE 的静态检查,更是生成文档和协作的关键。

from typing import List, Dict, Any

def merge_student_lists(list1: List[Dict[str, Any]], list2: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """
    合并两个学生字典列表,基于 school_id 进行聚合。
    
    Args:
        list1: 第一个学生列表
        list2: 第二个学生列表

    Returns:
        合并后的学生列表
    """
    merged: Dict[int, Dict[str, Any]] = {}
    
    # 提取通用逻辑以减少重复
    for source in [list1, list2]:
        for record in source:
            # 使用 .get() 增加健壮性,防止 KeyError
            sid = record.get(‘school_id‘)
            if sid is None:
                continue # 或者记录日志
                
            if sid not in merged:
                merged[sid] = {‘school_id‘: sid, ‘roll_no‘: []}
            
            # 确保即使是单一数值也能被处理为列表
            rolls = record.get(‘roll_no‘, [])
            if isinstance(rolls, list):
                merged[sid][‘roll_no‘].extend(rolls)
            else:
                merged[sid][‘roll_no‘].append(rolls)
                
    return list(merged.values())

这种改进后的代码展示了工程化思维:它处理了可能的脏数据,明确了输入输出类型,并且通过逻辑复用减少了代码量。

方法三:使用 Polars 库(应对大数据的现代选择)

虽然 Pandas 曾经是数据分析的王者,但在 2026 年,面对 流式数据大规模数据集Polars 凭借其 Rust 编写的高性能核心和懒加载 API,正逐渐成为数据处理的新宠。Polars 使用 Apache Arrow 格式,内存占用极低,速度极快。

为什么选择 Polars?

  • 零拷贝读取: 读取数据时不产生不必要的内存复制。
  • 多线程: 自动利用所有 CPU 核心。
  • 严格的类型检查: 相比 Pandas,Polars 对数据类型更敏感,减少了运行时错误。

代码示例

import polars as pl

# 初始化数据
Input1 = [{‘roll_no‘: [‘123445‘, ‘1212‘], ‘school_id‘: 1},
          {‘roll_no‘: [‘HA-4848231‘], ‘school_id‘: 2}]
Input2 = [{‘roll_no‘: [‘473427‘], ‘school_id‘: 2},
          {‘roll_no‘: [‘092112‘], ‘school_id‘: 5}]

# 步骤 1: 转换为 Polars DataFrame
# Polars 能够优雅地处理嵌套列表
df1 = pl.DataFrame(Input1)
df2 = pl.DataFrame(Input2)

# 步骤 2: 垂直合并
# 注意:Polars 的 concat 非常快
combined_df = pl.concat([df1, df2], how="vertical")

# 步骤 3: 分组聚合
# Polars 表达式 API 非常强大且链式友好
# explode 将列表展开成多行,group_by 聚合后再 collect 回列表
result_df = (
    combined_df
    .explode("roll_no") # 将 [‘a‘, ‘b‘] 变为多行 ‘a‘, ‘b‘
    .group_by("school_id")
    .agg(pl.col("roll_no")) # 聚合成列表
    .sort("school_id")
)

print("使用 Polars 合并后的结果:")
print(result_df.to_dicts())

输出结果

使用 Polars 合并后的结果:
[{‘school_id‘: 1, ‘roll_no‘: [‘123445‘, ‘1212‘]}, {‘school_id‘: 2, ‘roll_no‘: [‘HA-4848231‘, ‘473427‘]}, {‘school_id‘: 5, ‘roll_no‘: [‘092112‘]}]

Agentic AI 时代的思考

在我们最近构建的一个 Agentic AI 系统中,数据代理需要自主决定使用哪个库。我们发现,简单的列表合并用原生 Python,但如果涉及“清洗、转换、聚合”这一完整链条,AI 代理倾向于选择 Polars。这种自适应技术栈的选择正是未来开发的标志。

实战中的最佳实践与常见陷阱

在实际的开发工作中,仅仅“跑通代码”是不够的。我们还需要考虑代码的健壮性和边界情况。以下是你在编写此类代码时应该考虑的几个关键点:

1. 处理重复数据(去重)

在示例中,我们假设两个列表中的学号都是唯一的。但如果 Input1 和 Input2 中的 roll_no 列表包含重复的学号怎么办?上面的代码会简单地保留所有重复项。

解决方案: 在合并时使用集合来进行去重。

# 使用 set 去重的逻辑片段
if dict2[‘school_id‘] in merged_dict:
    # 将两个列表转为 set,合并后再转回 list
    unique_rolls = list(set(merged_dict[dict2[‘school_id‘]][‘roll_no‘]) | set(dict2[‘roll_no‘]))
    merged_dict[dict2[‘school_id‘]][‘roll_no‘] = unique_rolls
else:
    # ...添加逻辑

2. 字段缺失的情况

上面的代码假设每个字典里肯定有 INLINECODE373a2cbc 和 INLINECODE20579b6f。在现实世界的 API 或数据库导出中,数据可能是脏的。

解决方案: 使用 INLINECODEb20aa37d 来避免 INLINECODE27a0adc3,或者在循环开始时添加 try-except 块。

# 安全的访问方式
school_id = elem.get(‘school_id‘)
if school_id is not None:
    # 执行逻辑
else:
    # 记录错误日志或跳过
    print("Warning: Missing school_id in record")

3. 性能优化建议

  • 避免在循环中进行不必要的字符串操作:比如在遍历两个大列表时,尽量避免打印日志或格式化字符串,这在 Python 中会显著拖慢速度。
  • 使用生成器:如果你的数据量极大(比如几个 G 的日志文件),不要一次性把所有数据加载到列表里。考虑使用生成器或逐行读取,然后利用上面提到的 defaultdict 逐个处理。

总结

在 Python 中合并两个字典列表并没有唯一的标准答案,而是取决于你的具体上下文。

  • 如果你追求代码的简洁和高效collections.defaultdict 是你的不二之选。
  • 如果你只需要快速修复一个小脚本且不介意修改原列表,简单的 INLINECODE3521b6c0 循环配合 INLINECODE652cf455 是最快捷的。
  • 如果你在进行复杂的数据分析,Pandas 或者 Polars 将是最强力的工具。

希望这篇文章不仅帮你解决了如何合并列表的问题,还让你对 Python 数据结构的灵活运用有了更深的理解。编程的乐趣就在于根据不同的问题,找到最优雅的那把“钥匙”。

既然我们已经掌握了合并列表的技巧,为什么不试试把你手头的旧项目重构一下,或者尝试用 defaultdict 来优化你现有的数据处理流程呢?如果你有任何问题,或者想分享你独特的合并技巧,欢迎在评论区留言。

祝你编码愉快!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/47618.html
点赞
0.00 平均评分 (0% 分数) - 0