这是观察到的行为:
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])
[]
答案 0 :(得分:6)
itertools.groupby(iterable, key=None)
[...]
groupby()
的操作类似于Unix中的uniq过滤器。每次键函数的值发生变化时,它都会生成一个中断或新组(这就是为什么通常需要使用相同的键函数对数据进行排序)。这种行为不同于SQL的GROUP BY,它聚合了常见元素而不管它们的输入顺序如何。返回的组本身就是一个迭代器,它与
groupby()
共享底层的iterable。因为源是共享的,所以当`groupby()对象被提前时,前一个组不再可见。因此,如果稍后需要该数据,应将其存储为列表 [ - ]
因此,最后一段中的假设是生成的列表将是空列表[]
,因为迭代器已经提前,并且遇到StopIteration
;但是在CPython中,结果令人惊讶[9]
。
这是因为_grouper
iterator滞后原始迭代器后面的一个项目,这是因为groupby
需要先查看一个项目以查看它是属于当前还是下一个组,但它必须以后可以将此项目作为新组的第一项。
然而currkey
的{{1}}和currvalue
属性在original iterator is exhausted时未重置,因此groupby
仍然指向到迭代器的最后一项。
CPython文档实际上包含此等效代码,它也具有与C版本代码完全相同的行为:
currvalue
值得注意的是,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
时,StopItertion
仍包含前一组的最后一个密钥。现在,当self.currvalue
成为y[1]
时,第一个会产生list
的值,然后才会在底层迭代器上运行self.currvalue
(并再次与next()
见面。)
即使文档中有Python等价物,其行为与CPython中的权威C代码实现完全相同,但IronPython,Jython和PyPy会产生不同的结果。
答案 1 :(得分:2)
问题是您将所有这些组合成一个组,因此在第一次next
调用后,所有内容都已分组:
import itertools
x = itertools.groupby(range(10), lambda x: True)
key, elements = next(x)
但是elements
是一个生成器,所以你需要立即将它传递到某个结构中,使用一个可迭代的“打印”或“保存”它,即list
:
print('Key: "{}" with value "{}"'.format(key, list(elements)))
然后你的range(10)
为空,并且groupy-generator已完成:
Key: True with value [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]