Navigation Menu

Skip to content

Commit

Permalink
bpo-30346: An iterator produced by the itertools.groupby() iterator (#…
Browse files Browse the repository at this point in the history
…1569)

now becames exhausted after advancing the groupby iterator.
  • Loading branch information
serhiy-storchaka committed Sep 24, 2017
1 parent 4facdf5 commit c247caf
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 3 deletions.
7 changes: 4 additions & 3 deletions Doc/library/itertools.rst
Expand Up @@ -401,13 +401,14 @@ loops that truncate the stream.
def __iter__(self):
return self
def __next__(self):
self.id = object()
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:
return (self.currkey, self._grouper(self.tgtkey, self.id))
def _grouper(self, tgtkey, id):
while self.id is id and self.currkey == tgtkey:
yield self.currvalue
try:
self.currvalue = next(self.it)
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_itertools.py
Expand Up @@ -751,6 +751,26 @@ def test_groupby(self):
self.assertEqual(set(keys), expectedkeys)
self.assertEqual(len(keys), len(expectedkeys))

# Check case where inner iterator is used after advancing the groupby
# iterator
s = list(zip('AABBBAAAA', range(9)))
it = groupby(s, testR)
_, g1 = next(it)
_, g2 = next(it)
_, g3 = next(it)
self.assertEqual(list(g1), [])
self.assertEqual(list(g2), [])
self.assertEqual(next(g3), ('A', 5))
list(it) # exhaust the groupby iterator
self.assertEqual(list(g3), [])

for proto in range(pickle.HIGHEST_PROTOCOL + 1):
it = groupby(s, testR)
_, g = next(it)
next(it)
next(it)
self.assertEqual(list(pickle.loads(pickle.dumps(g, proto))), [])

# Exercise pipes and filters style
s = 'abracadabra'
# sort s | uniq
Expand Down
@@ -0,0 +1,2 @@
An iterator produced by itertools.groupby() iterator now becames exhausted
after advancing the groupby iterator.
8 changes: 8 additions & 0 deletions Modules/itertoolsmodule.c
Expand Up @@ -17,6 +17,7 @@ typedef struct {
PyObject *tgtkey;
PyObject *currkey;
PyObject *currvalue;
const void *currgrouper; /* borrowed reference */
} groupbyobject;

static PyTypeObject groupby_type;
Expand Down Expand Up @@ -77,6 +78,7 @@ groupby_next(groupbyobject *gbo)
{
PyObject *newvalue, *newkey, *r, *grouper;

gbo->currgrouper = NULL;
/* skip to next iteration group */
for (;;) {
if (gbo->currkey == NULL)
Expand Down Expand Up @@ -255,6 +257,7 @@ _grouper_create(groupbyobject *parent, PyObject *tgtkey)
Py_INCREF(parent);
igo->tgtkey = tgtkey;
Py_INCREF(tgtkey);
parent->currgrouper = igo; /* borrowed reference */

PyObject_GC_Track(igo);
return (PyObject *)igo;
Expand Down Expand Up @@ -284,6 +287,8 @@ _grouper_next(_grouperobject *igo)
PyObject *newvalue, *newkey, *r;
int rcmp;

if (gbo->currgrouper != igo)
return NULL;
if (gbo->currvalue == NULL) {
newvalue = PyIter_Next(gbo->it);
if (newvalue == NULL)
Expand Down Expand Up @@ -321,6 +326,9 @@ _grouper_next(_grouperobject *igo)
static PyObject *
_grouper_reduce(_grouperobject *lz)
{
if (((groupbyobject *)lz->parent)->currgrouper != lz) {
return Py_BuildValue("N(())", _PyObject_GetBuiltin("iter"));
}
return Py_BuildValue("O(OO)", Py_TYPE(lz), lz->parent, lz->tgtkey);
}

Expand Down

0 comments on commit c247caf

Please sign in to comment.