itertools.groupby转列表后非最后组为空的原因探究
为什么itertools.groupby转成列表后分组迭代器为空?
这问题我当初第一次用itertools.groupby的时候也踩过一模一样的大坑!盯着输出的空列表愣了半天,后来翻了文档才搞清楚核心原因:
核心问题:groupby的分组迭代器是「绑定底层状态」的惰性迭代器
itertools.groupby返回的不是一个包含所有分组内容的容器,而是一个惰性迭代器。每次从这个迭代器里取出的(key, grouper)对中,grouper本身也是一个迭代器——它没有提前缓存分组内容,而是直接依赖原序列的当前遍历位置。
当你执行list(itertools.groupby(l, key=lambda x:x[0]))时,你只是把三个(键, 分组迭代器)的引用存到了列表里,但这个过程中groupby迭代器已经把原序列从头到尾遍历了一遍,用来确定每个分组的边界。此时,前面的分组迭代器对应的原序列位置已经被推进到了后面的分组区域,甚至序列末尾:
- 生成第一个
(1, grouper1)后,groupby会继续遍历原序列,直到找到第一个键不为1的元素(也就是(2, 'b')),此时原序列的指针停在了这里。 - 当你后续再去遍历
grouper1时,它会从当前指针位置开始取元素,但第一个元素的键是2,不符合分组键1,所以直接返回空列表。 - 同理,
grouper2对应的指针位置已经到了(3, 'd'),遍历它时也找不到键为2的元素,返回空。 - 最后一个
grouper3因为是最后生成的,指针停在了序列末尾附近,所以只能取到最后一个元素(这也是你看到[(3, 'f')]的原因)。
正确的做法:遍历groupby时立即缓存分组内容
如果你想保存所有分组的结果,必须在遍历groupby迭代器的同时,把每个grouper转成列表(或其他容器),而不是先保存grouper对象。代码如下:
import itertools l = list(zip([1,2,2,3,3,3],['a','b','c','d','e','f'])) # 遍历groupby的同时就将分组转成列表保存 grouped_l = [(key, list(grouper)) for key, grouper in itertools.groupby(l, key=lambda x:x[0])] print(grouped_l) # 输出: [(1, [(1, 'a')]), (2, [(2, 'b'), (2, 'c')]), (3, [(3, 'd'), (3, 'e'), (3, 'f')])]
这样每个分组的内容都会被及时缓存下来,不会因为底层指针移动而丢失。
额外提醒:groupby要求原序列先按分组键排序!
差点忘了一个重要细节:itertools.groupby只会把连续相同键的元素分到一组。如果你的原序列没有按分组键排序,会出现分组不完整的情况。比如如果原序列是[(1,a), (2,b), (1,c)],groupby会分成两组(1, [(1,a)])和(2, [(2,b)]),然后又一个(1, [(1,c)]),而不是把所有键为1的元素分到一起。如果需要按键聚合所有元素,记得先排序:
# 先按分组键排序 sorted_l = sorted(l, key=lambda x:x[0]) grouped_l = [(key, list(grouper)) for key, grouper in itertools.groupby(sorted_l, key=lambda x:x[0])]
内容的提问来源于stack exchange,提问作者peer
相关产品推荐
相关产品推荐

