Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c247caf

Browse files
bpo-30346: An iterator produced by the itertools.groupby() iterator (#1569)
now becames exhausted after advancing the groupby iterator.
1 parent 4facdf5 commit c247caf

4 files changed

Lines changed: 34 additions & 3 deletions

File tree

Doc/library/itertools.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,13 +401,14 @@ loops that truncate the stream.
401401
def __iter__(self):
402402
return self
403403
def __next__(self):
404+
self.id = object()
404405
while self.currkey == self.tgtkey:
405406
self.currvalue = next(self.it) # Exit on StopIteration
406407
self.currkey = self.keyfunc(self.currvalue)
407408
self.tgtkey = self.currkey
408-
return (self.currkey, self._grouper(self.tgtkey))
409-
def _grouper(self, tgtkey):
410-
while self.currkey == tgtkey:
409+
return (self.currkey, self._grouper(self.tgtkey, self.id))
410+
def _grouper(self, tgtkey, id):
411+
while self.id is id and self.currkey == tgtkey:
411412
yield self.currvalue
412413
try:
413414
self.currvalue = next(self.it)

Lib/test/test_itertools.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,26 @@ def test_groupby(self):
751751
self.assertEqual(set(keys), expectedkeys)
752752
self.assertEqual(len(keys), len(expectedkeys))
753753

754+
# Check case where inner iterator is used after advancing the groupby
755+
# iterator
756+
s = list(zip('AABBBAAAA', range(9)))
757+
it = groupby(s, testR)
758+
_, g1 = next(it)
759+
_, g2 = next(it)
760+
_, g3 = next(it)
761+
self.assertEqual(list(g1), [])
762+
self.assertEqual(list(g2), [])
763+
self.assertEqual(next(g3), ('A', 5))
764+
list(it) # exhaust the groupby iterator
765+
self.assertEqual(list(g3), [])
766+
767+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
768+
it = groupby(s, testR)
769+
_, g = next(it)
770+
next(it)
771+
next(it)
772+
self.assertEqual(list(pickle.loads(pickle.dumps(g, proto))), [])
773+
754774
# Exercise pipes and filters style
755775
s = 'abracadabra'
756776
# sort s | uniq
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
An iterator produced by itertools.groupby() iterator now becames exhausted
2+
after advancing the groupby iterator.

Modules/itertoolsmodule.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ typedef struct {
1717
PyObject *tgtkey;
1818
PyObject *currkey;
1919
PyObject *currvalue;
20+
const void *currgrouper; /* borrowed reference */
2021
} groupbyobject;
2122

2223
static PyTypeObject groupby_type;
@@ -77,6 +78,7 @@ groupby_next(groupbyobject *gbo)
7778
{
7879
PyObject *newvalue, *newkey, *r, *grouper;
7980

81+
gbo->currgrouper = NULL;
8082
/* skip to next iteration group */
8183
for (;;) {
8284
if (gbo->currkey == NULL)
@@ -255,6 +257,7 @@ _grouper_create(groupbyobject *parent, PyObject *tgtkey)
255257
Py_INCREF(parent);
256258
igo->tgtkey = tgtkey;
257259
Py_INCREF(tgtkey);
260+
parent->currgrouper = igo; /* borrowed reference */
258261

259262
PyObject_GC_Track(igo);
260263
return (PyObject *)igo;
@@ -284,6 +287,8 @@ _grouper_next(_grouperobject *igo)
284287
PyObject *newvalue, *newkey, *r;
285288
int rcmp;
286289

290+
if (gbo->currgrouper != igo)
291+
return NULL;
287292
if (gbo->currvalue == NULL) {
288293
newvalue = PyIter_Next(gbo->it);
289294
if (newvalue == NULL)
@@ -321,6 +326,9 @@ _grouper_next(_grouperobject *igo)
321326
static PyObject *
322327
_grouper_reduce(_grouperobject *lz)
323328
{
329+
if (((groupbyobject *)lz->parent)->currgrouper != lz) {
330+
return Py_BuildValue("N(())", _PyObject_GetBuiltin("iter"));
331+
}
324332
return Py_BuildValue("O(OO)", Py_TYPE(lz), lz->parent, lz->tgtkey);
325333
}
326334

0 commit comments

Comments
 (0)