如何用itertools.groupby正确分组字典列表?Tortoise-ORM分组异常排查
解决itertools.groupby分组丢失数据与关联字段访问问题
一、分组丢失数据的核心原因:未提前排序
itertools.groupby的本质是对连续相同的键进行分组,并非全局聚合。如果你的Assignment查询结果没有按project_id排序,相同project_id的记录会分散在结果集的不同位置,groupby只会将连续的归为一组,直接导致大量数据被遗漏。
修复方案
查询时必须先按分组键排序,结合Tortoise-ORM的写法:
from itertools import groupby from your_app.models import Project, Assignment # 过滤日期后,按project_id排序,同时预加载关联的project assignments = await Assignment.filter( date__range=(start_date, end_date) ).select_related("project").order_by("project_id") # 关键:添加order_by保证同project_id的记录连续 # 执行分组 grouped_projects = groupby(assignments, key=lambda x: x.project_id)
二、二次分组访问关联字段的问题
访问x.collaborator.job_id出错,大概率是未预加载关联对象导致的懒加载异常。Tortoise-ORM默认懒加载关联数据,当groupby迭代完成后,原查询集的数据库连接可能已关闭,此时再访问未预加载的关联字段会抛出错误。
修复方案
- 提前用
select_related(适用于一对一/多对一关联)预加载collaborator对象 - 二次分组前,同样要对分组键排序,避免再次出现分组遗漏
- groupby返回的迭代器只能遍历一次,需先转成列表保存
示例代码:
# 预加载project和collaborator,同时按project_id排序 assignments = await Assignment.filter( date__range=(start_date, end_date) ).select_related("project", "collaborator").order_by("project_id") # 一级分组:按project_id result = [] for project_id, project_assigns_iter in grouped_projects: # 将迭代器转为列表,避免后续无法二次遍历 project_assigns = list(project_assigns_iter) # 二级分组前,先按collaborator.job_id排序 project_assigns.sort(key=lambda x: x.collaborator.job_id) grouped_jobs = groupby(project_assigns, key=lambda x: x.collaborator.job_id) # 组装返回的JSON结构 project_data = { "project_id": project_id, "project_name": project_assigns[0].project.name, # 已预加载project,可直接访问 "job_groups": [ { "job_id": job_id, "assignments": [{"id": assign.id, "content": assign.content} for assign in assigns] } for job_id, assigns in grouped_jobs ] } result.append(project_data)
常见误区总结
- 忘记排序:groupby只处理连续相同的键,必须先按分组键排序后再分组
- 迭代器重复使用:groupby返回的迭代器只能遍历一次,二次分组前必须转成列表
- 懒加载关联字段:未预加载的关联字段在查询集迭代完成后无法访问,必须用
select_related/prefetch_related提前加载 - 分组键与排序键不一致:比如分组用
x.project.id,排序也要对应用project__id,否则排序失效
内容的提问来源于stack exchange,提问作者Diegol
相关产品推荐
相关产品推荐

