在处理实际的数据处理任务时,我们经常会遇到这样的情况:手头有两个包含字典的列表,每个字典代表一个实体的信息,而我们的目标是基于某个共同的“键”(比如 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)
在编写上述逻辑时,我们现在的团队会大量使用 Cursor 或 Windsurf 这样的 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 来优化你现有的数据处理流程呢?如果你有任何问题,或者想分享你独特的合并技巧,欢迎在评论区留言。
祝你编码愉快!