itertools.groupby的意外行为

检查员

这是观察到的行为:

In [4]: x = itertools.groupby(range(10), lambda x: True)

In [5]: y = next(x)

In [6]: next(x)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-6-5e4e57af3a97> in <module>()
----> 1 next(x)

StopIteration: 

In [7]: y
Out[7]: (True, <itertools._grouper at 0x10a672e80>)

In [8]: list(y[1])
Out[8]: [9]

的预期输出list(y[1])[0,1,2,3,4,5,6,7,8,9]

这里发生了什么?

我观察到了这一点cpython 3.4.2,但其他人通过cpython 3.5发现了这一点IronPython 2.9.9a0 (2.9.0.0) on Mono 4.0.30319.17020 (64-bit)

Jython 2.7.0和pypy上观察到的行为

Python 2.7.10 (5f8302b8bf9f, Nov 18 2015, 10:46:46)
[PyPy 4.0.1 with GCC 4.8.4]

>>>> x = itertools.groupby(range(10), lambda x: True)
>>>> y = next(x)
>>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>> y
(True, <itertools._groupby object at 0x00007fb1096039a0>)
>>>> list(y[1])
[]
安蒂·哈帕拉(Antti Haapala)

itertools.groupby 文档告诉我们

itertools.groupby(iterable, key=None)

[...]

的操作groupby()类似于Unix中的uniq过滤器。每当键函数的值更改时,它都会生成一个中断或新组(这就是为什么通常需要使用相同的键函数对数据进行排序的原因)。这种行为与SQL的GROUP BY不同,后者的GROUP BY聚集公共元素,而不管它们的输入顺序如何。

返回的组本身就是一个与共享底层可迭代对象的迭代器groupby()因为源是共享的,所以当`groupby()“对象前进时,先前的组不再可见。因此,如果以后需要该数据,则应将其存储为列表[-]

因此,从最后一段开始假设[],由于迭代器已经前进并满足,因此生成的列表将是空列表StopIteration但是在CPython中结果却令人惊讶[9]


这是因为_grouper迭代器在原始迭代器之后滞后了一个项目,这是因为groupby需要先窥视一个项目以查看它是否属于当前组或下一个组,但它必须能够稍后将其作为第一个项目生成。新组。

然而,currkeycurrvalue属性groupby没有当复位原来迭代器被耗尽,所以currvalue点还是从迭代器的最后一个项目。

CPython文档实际上包含此等效代码,并且其行为与C版本代码完全相同:

class groupby:
    # [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
    # [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
    def __init__(self, iterable, key=None):
        if key is None:
            key = lambda x: x
        self.keyfunc = key
        self.it = iter(iterable)
        self.tgtkey = self.currkey = self.currvalue = object()
    def __iter__(self):
        return self
    def __next__(self):
        while self.currkey == self.tgtkey:
            self.currvalue = next(self.it)    # Exit on StopIteration
            self.currkey = self.keyfunc(self.currvalue)
        self.tgtkey = self.currkey
        return (self.currkey, self._grouper(self.tgtkey))
    def _grouper(self, tgtkey):
        while self.currkey == tgtkey:
            yield self.currvalue
            try:
                self.currvalue = next(self.it)
            except StopIteration:
                return
            self.currkey = self.keyfunc(self.currvalue)

值得注意的是,__next__发现找到下一组的第一项,并将其密钥存储到中,self.currkey并将其值存储到中self.currvalue但关键是线

self.currvalue = next(self.it)    # Exit on StopIteration

next引发时StopItertionself.currvalue静止图像仍包含前一组的最后一个键。现在,当y[1]变成a时list,它首先产生的值self.currvalue,然后才next()在基础迭代器上运行(并StopIteration再次遇到)。


即使文档中有等效的Python,其行为也与CPython中权威的C代码实现完全一样,IronPython,Jython和PyPy会给出不同的结果。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章