Python3.6中itertools.groupby的反直觉行为技术问询
为什么
itertools.groupby返回的组迭代器是空的? 哈哈,这个坑我也踩过好几次!itertools.groupby的这个行为确实非常反直觉,尤其是当你先把它的结果转成list之后再处理的时候,问题出在两个核心点上:
1. groupby的组迭代器是“一次性”且依赖原迭代器状态的
itertools.groupby不会提前把所有组的元素都存下来,它是惰性遍历输入序列的。当你调用groupby时,它返回的是一个迭代器,每次生成一个组(键+组迭代器)。而每个组的迭代器并不是独立的——它和groupby的主迭代器共享同一个输入序列的位置。
也就是说:
- 当你获取下一个组的时候,
groupby会先把当前组的所有元素都遍历完(跳过),直到遇到新的键,才会生成新的组。 - 如果你先把所有组都存到
list里(比如groups = list(groupby(...))),那么在生成这个list的过程中,groupby已经把整个输入序列遍历了一遍,前面的组的迭代器已经没有元素可以取了——因为输入序列的指针已经走到了后面的位置。
2. 你的代码的问题所在
你把groupby的结果转成了list,这就导致在生成list的过程中:
- 第一个组(键
'5')的迭代器在groupby生成第二个组时,已经被耗尽了(因为groupby要跳过所有'5'的元素才能找到'10'的元素)。 - 同理,第二个组(键
'10')的迭代器在生成第三个组时被耗尽,第三个组(键'20')的迭代器在生成第四个组时被耗尽。 - 只有最后一个组(键
'100')的迭代器还能取到元素,因为后面没有新的键了,groupby没有跳过它的元素。
解决方法
有两种正确的做法:
方法一:直接遍历groupby的结果,不转成list
在获取每个组的时候直接处理元素,不要先把组存起来:
import itertools a = ['1D.5', '2D.5', '3D.5', '1D.10', '2D.10', '3D.10', '1D.20', '2D.20', '3D.20', '1D.100', '2D.100', '3D.100'] for gname, glist in itertools.groupby(a, key=lambda x: x.split('.')[-1]): print(list(glist))
这样运行会得到正确的结果:
['1D.5', '2D.5', '3D.5'] ['1D.10', '2D.10', '3D.10'] ['1D.20', '2D.20', '3D.20'] ['1D.100', '2D.100', '3D.100']
方法二:提前把每个组的元素存成list
如果你需要把所有组都保存下来之后再处理,可以在生成组的时候就把元素转成list:
import itertools a = ['1D.5', '2D.5', '3D.5', '1D.10', '2D.10', '3D.10', '1D.20', '2D.20', '3D.20', '1D.100', '2D.100', '3D.100'] groups = [(gname, list(glist)) for gname, glist in itertools.groupby(a, key=lambda x: x.split('.')[-1])] for gname, glist in groups: print(glist)
这样groups里的每个组都已经是保存好的元素列表,后续遍历不会有问题。
额外提醒:groupby要求输入序列按分组键排序
哦,对了,还有一个容易踩的坑:groupby只会把连续相同键的元素分到一组。如果你的输入序列没有按分组键排序,即使有相同键的元素,也会被分到不同的组里。比如如果你的列表是['1D.5', '1D.10', '2D.5'],按'.'后的部分分组的话,'1D.5'和'2D.5'会被分成两个组。不过在你的例子里,输入序列已经是按分组键排序好的,所以这个问题没出现,但还是要记住这个点!
内容的提问来源于stack exchange,提问作者Dilawar
相关产品推荐
相关产品推荐

