你是不是也遇到过这种事儿?手里攥着一堆乱糟糟的数据,可能是一个长长的列表,里面装着一堆字典,就像这样:

python
sales_data = [
{'city': '上海', 'product': 'A', 'amount': 100},
{'city': '北京', 'product': 'B', 'amount': 150},
{'city': '上海', 'product': 'B', 'amount': 200},
{'city': '深圳', 'product': 'A', 'amount': 50},
{'city': '北京', 'product': 'A', 'amount': 120},
{'city': '上海', 'product': 'A', 'amount': 80},
]

然后,你的老板或者产品经理,甩给你一个需求:“小王啊,给我按城市统计一下销售额。”

这时候你脑子里第一反应是啥?是不是立马就想撸起袖子写个 for 循环,搞个字典,然后 if city in result_dict 这样一顿操作?

别,千万别。

我刚开始写 Python 的时候也这么干,代码写得又臭又长,自己回头看都嫌弃。先创建一个空字典,然后遍历整个列表,判断城市在不在字典的键里,在就累加,不在就新建……逻辑不复杂,但就是感觉特别“笨拙”。这就像你想把一堆硬币按面值分开,你不用点币机,非得一枚一枚拿起来看,再扔到对应的罐子里。效率呢?优雅呢?都没了。

直到有一天,我发现了 itertools 这个宝藏模块,里面的 itertools.groupby 简直就是为解决 Python 怎么分组 这个问题而生的神兵利器。

说真的,当你第一次用它,你会有一种豁然开朗的感觉。什么叫 Pythonic?这就是。

itertools.groupby:分组界的“艺术家”

我们先直接上代码,看看用 groupby 怎么解决上面那个问题。记住,用 groupby 有个大前提,一个非常非常重要的 “潜规则”—— 数据必须先按照分组的键进行排序。不排序,它就不好好干活,这个坑我们后面细说,你先记住。

“`python
from itertools import groupby
from operator import itemgetter

先排序!先排序!先排序!重要的事情说三遍

sales_data.sort(key=itemgetter(‘city’))

现在可以愉快地分组了

grouped_data = groupby(sales_data, key=itemgetter(‘city’))

for city, group in grouped_data:
# 这里的 group 是一个迭代器,不是列表哦
total_amount = sum(item[‘amount’] for item in group)
print(f”城市: {city}, 总销售额: {total_amount}”)
输出会是这样:
城市: 上海, 总销售额: 380
城市: 北京, 总销售额: 270
城市: 深圳, 总销售额: 50
``
你看,是不是干净利落多了?没有了
if-else` 的判断,整个逻辑就是:排序、分组、处理。一条流水线下来,清清楚楚。

这里的 key=itemgetter('city')lambda item: item['city'] 效果一模一样,都是告诉 groupby:“嘿,你就看每个字典里的’city’这个键,一样的就给我归为一类!” itemgetter 性能上会稍微好那么一丢丢,而且看起来更专业,你可以装个小小的B。

那个必须排序的“坑”,其实是它的工作原理

好了,现在我们来聊聊为啥非得先排序。

很多人第一次用 itertools.groupby 都掉这个坑里。他们不排序,直接分组,结果发现分组结果奇奇怪怪,根本不对。然后就大骂:“这什么破玩意儿!”

其实,你误会它了。groupby 的工作方式非常“耿直”,它不像我们想象中那样,先把所有数据看一遍,再把相同的归拢到一起。不,它很懒,它只看相邻的元素。

你可以把 groupby 想象成一个流水线上的质检员。传送带上过来一堆产品(你的数据列表),他手里拿着当前要找的产品的标签(比如“上海”)。

  1. 第一个产品来了,是“上海”的,OK,他划到“上海”这个筐里。
  2. 第二个产品来了,还是“上海”的,继续划到“上海”筐里。
  3. 第三个产品来了,突然变成了“北京”的。质检员立马就把“上海”这个筐封箱打包(一个分组完成了),然后拿出“北京”的标签,开始找“北京”的产品。
  4. 如果后面又来了一个“上海”的产品,质检员会怎么做?他不会再打开刚才封好的“上海”箱子。他会认为这是一个 新的 分组,然后重新给它一个“上海”的筐。

这就是为什么不排序,分组结果会乱七八糟。因为只要key变了,groupby 就认为上一个分组结束了。所以,在使用 groupby 之前,先用相同的 key 对数据进行排序,确保所有相同键的元素都挨在一起,这样质检员才能把它们都放到同一个筐里。

这算不上是 groupby 的缺点,而是它的设计哲学:高效、专注、只做一件事。它只负责“切分”,把连续相同的元素块给找出来。排序这个活儿,你自己先干好。

groupby 返回的到底是个啥?

你可能注意到我代码注释里写了,group 是一个迭代器。这也是个小小的“陷阱”。

for city, group in grouped_data: 这个循环里,city 就是你分组的那个键(比如“上海”),而 group 并不是一个列表 [{...}, {...}],它是一个迭代器。

迭代器意味着什么?

  1. 一次性: 你遍历完一遍,它就空了。如果你想多次使用一个分组里的数据,记得第一时间把它转换成列表:list(group)
  2. 惰性计算: 它不会立刻把所有分组数据都加载到内存里,这在处理海量数据时,简直是救星,能省下大把内存。

所以,最常见的用法,就是像我上面那样,在循环里直接处理掉 group 这个迭代器,比如求和、计数,或者把它转换成列表存起来。

“`python

一个更常见的模式:用字典推导式一步到位

sales_data.sort(key=itemgetter(‘city’))
grouped_data = groupby(sales_data, key=itemgetter(‘city’))

result = {city: list(items) for city, items in grouped_data}

result 现在就是一个字典,键是城市,值是该城市所有销售记录的列表

print(result)
“`

除了 groupby,还有别的玩法吗?

当然有!Python 怎么分组 的答案不止一个。itertools.groupby 是标准库里的精锐,但也不是唯一的选择。

有时候,你的需求可能就是不想排序,或者说,排序的成本太高,数据量又不大。那我们完全可以回归“传统手艺”,但用更 Pythonic 的方式来写。

比如,用 collections.defaultdict

“`python
from collections import defaultdict

grouped_by_city = defaultdict(list)

for sale in sales_data: # 注意,这里不需要排序了
grouped_by_city[sale[‘city’]].append(sale)

grouped_by_city 现在就是一个字典,键是城市,值是销售记录列表

它比手动 if/else 判断要优雅得多

“`

defaultdict(list) 的意思是,创建一个字典,当你试图访问一个不存在的键时,它不会报错,而是会自动给你创建一个默认值——在这里就是一个空列表 []。这不就完美省去了那个 if key not in dict 的判断嘛!

终极武器:Pandas 的 groupby

如果你的数据量上来了,比如要处理几万、几十万甚至上百万行的数据,而且分组后的聚合操作(求和、求平均、计数等)非常复杂。那么,别犹豫了,上 pandas 吧。

pandas 是数据科学领域的王者,它的 groupby 功能强大到令人发指。

“`python
import pandas as pd

df = pd.DataFrame(sales_data)
grouped = df.groupby(‘city’)[‘amount’].sum()
print(grouped)
``
就这么简单两行,结果就出来了,而且返回的是一个结构清晰的
Series对象。pandasgroupby背后做了大量的优化,性能极高,而且支持的聚合函数也极其丰富。它就像是处理分组问题的“航母战斗群”,而itertools.groupby` 则是灵活快速的“特种部队”。

所以,Python 怎么分组

  • 小数据、一次性脚本、追求标准库的极致优雅:请拥抱 itertools.groupby,但死死记住要 先排序
  • 不想排序,或者逻辑简单collections.defaultdict 是你贴心的小棉袄。
  • 大数据、专业数据分析、复杂的聚合需求:别想了,直接请出 pandas 这尊大神。

掌握了这些,下次再面对一堆乱麻般的数据时,你就能像个经验丰富的老手,从容地选出最合适的武器,一招制敌。这,才是真正的 Pythonic之道。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。