为什么itertools.groupby分组只能迭代一次?

时间:2016-01-15 18:57:28

标签: python itertools

我最近不得不调试一些像这样的代码:

for key, group in itertools.groupby(csvGrid, lambda x: x[0]):
    value1 = sum(row[1] for row in group)
    value2 = sum(row[2] for row in group)
    results.append([key, value1, value2])

在每个结果集中,value2都显示为0。当我查看它时,我发现代码第一次迭代group时,它消耗了它,所以第二次有零元素迭代。

直观地说,我希望group是一个可以无限次迭代的列表,但它的行为就像一个只能迭代一次的迭代器。有这么好的理由吗?

4 个答案:

答案 0 :(得分:4)

itertools是一个迭代器库,就像库中的其他所有内容一样,itertools.groupby组是迭代器。所有itertools中都没有一个函数可以返回序列。

groupby组是迭代器的原因与itertools中的其他所有东西都是迭代器的原因相同:

  1. 内存效率更高。
  2. 这些团体可能是无限的。
  3. 您可以立即获得结果,而不是等待整个群组做好准备。
  4. 此外,这些组是迭代器,因为您可能只需要键,在这种情况下实现组将是一种浪费。

    itertools.groupby无意与任何LINQ构造,SQL子句或其他名称为“group by”的东西完全匹配。它的分组行为更接近于Unix的uniq命令的扩展,而不是LINQ或SQL的扩展,尽管它使组成为一个意味着它不是uniq的完全匹配。

    作为你可以用itertools.groupby做的事情的例子,你不能用我命名的其他工具,这里是一个游程编码器:

    def runlengthencode(iterable):
        for key, group in groupby(iterable):
            yield (key, sum(1 for val in group))
    

答案 1 :(得分:2)

  

直观地说,我希望group是一个可以无限次迭代的列表,但它的行为就像一个只能迭代一次的迭代器。

那是对的。

  

有这么好的理由吗?

  1. 它可能更具内存效率:您不需要先构建整个列表,然后将其存储在内存中,然后再迭代它。相反,您可以在迭代时处理元素。

  2. 它可能提高CPU效率:通过不预先生成所有数据,例如:通过生成一个列表,你可以提前挽救:如果你找到一个匹配某个谓词的特定组,你可以停止迭代 - 不需要做进一步的工作。

  3. 是否需要所有数据并多次迭代它的决定不是被调用者硬编码的,而是留给调用者。

答案 2 :(得分:1)

来自docs

  

返回的组本身就是一个迭代器,它与groupby()共享底层的iterable。由于源是共享的,因此当groupby()对象处于高级时,前一个组将不再可见。因此,如果以后需要该数据,则应将其存储为列表

有趣的是,如果您不自己消耗ggroupby将在返回下一次迭代之前执行此操作。

>>> def vals():
...     for i in range(10):
...         print(i)
...         yield i
... 
>>> for k,g in itertools.groupby(vals(), lambda x: x<5):
...     print('processing group')
... 
0
processing group
1
2
3
4
5
processing group
6
7
8
9

答案 3 :(得分:0)

当尝试多次访问“ groupby”返回的迭代器时,我遇到了相同的问题。 基于Python3 doc,建议将迭代器传输到list,以便以后可以访问。