8000 GH-98363: Have batched() return tuples (GH-100118) · python/cpython@35cc0ea · GitHub
[go: up one dir, main page]

Skip to content

Commit 35cc0ea

Browse files
authored
GH-98363: Have batched() return tuples (GH-100118)
1 parent 41d4ac9 commit 35cc0ea

File tree

4 files changed

+40
-38
lines changed

4 files changed

+40
-38
lines changed

Doc/library/itertools.rst

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Iterator Arguments Results
5252
Iterator Arguments Results Example
5353
============================ ============================ ================================================= =============================================================
5454
:func:`accumulate` p [,func] p0, p0+p1, p0+p1+p2, ... ``accumulate([1,2,3,4,5]) --> 1 3 6 10 15``
55-
:func:`batched` p, n [p0, p1, ..., p_n-1], ... ``batched('ABCDEFG', n=3) --> ABC DEF G``
55+
:func:`batched` p, n (p0, p1, ..., p_n-1), ... ``batched('ABCDEFG', n=3) --> ABC DEF G``
5656
:func:`chain` p, q, ... p0, p1, ... plast, q0, q1, ... ``chain('ABC', 'DEF') --> A B C D E F``
5757
:func:`chain.from_iterable` iterable p0, p1, ... plast, q0, q1, ... ``chain.from_iterable(['ABC', 'DEF']) --> A B C D E F``
5858
:func:`compress` data, selectors (d[0] if s[0]), (d[1] if s[1]), ... ``compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F``
@@ -166,11 +166,11 @@ loops that truncate the stream.
166166

167167
.. function:: batched(iterable, n)
168168

169-
Batch data from the *iterable* into lists of length *n*. The last
169+
Batch data from the *iterable* into tuples of length *n*. The last
170170
batch may be shorter than *n*.
171171

172-
Loops over the input iterable and accumulates data into lists up to
173-
size *n*. The input is consumed lazily, just enough to fill a list.
172+
Loops over the input iterable and accumulates data into tuples up to
173+
size *n*. The input is consumed lazily, just enough to fill a batch.
174174
The result is yielded as soon as the batch is full or when the input
175175
iterable is exhausted:
176176

@@ -179,14 +179,14 @@ loops that truncate the stream.
179179
>>> flattened_data = ['roses', 'red', 'violets', 'blue', 'sugar', 'sweet']
180180
>>> unflattened = list(batched(flattened_data, 2))
181181
>>> unflattened
182-
[['roses', 'red'], ['violets', 'blue'], ['sugar', 'sweet']]
182+
[('roses', 'red'), ('violets', 'blue'), ('sugar', 'sweet')]
183183

184184
>>> for batch in batched('ABCDEFG', 3):
185185
... print(batch)
186186
...
187-
['A', 'B', 'C']
188-
['D', 'E', 'F']
189-
['G']
187+
('A', 'B', 'C')
188+
('D', 'E', 'F')
189+
('G',)
190190

191191
Roughly equivalent to::
192192

@@ -195,7 +195,7 @@ loops that truncate the stream.
195195
if n < 1:
196196
raise ValueError('n must be at least one')
197197
it = iter(iterable)
198-
while (batch := list(islice(it, n))):
198+
while (batch := tuple(islice(it, n))):
199199
yield batch
200200

201201
.. versionadded:: 3.12

Lib/test/test_itertools.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,11 @@ def test_accumulate(self):
161161

162162
def test_batched(self):
163163
self.assertEqual(list(batched('ABCDEFG', 3)),
164-
[['A', 'B', 'C'], ['D', 'E', 'F'], ['G']])
164+
[('A', 'B', 'C'), ('D', 'E', 'F'), ('G',)])
165165
self.assertEqual(list(batched('ABCDEFG', 2)),
166-
[['A', 'B'], ['C', 'D'], ['E', 'F'], ['G']])
166+
[('A', 'B'), ('C', 'D'), ('E', 'F'), ('G',)])
167167
self.assertEqual(list(batched('ABCDEFG', 1)),
168-
[['A'], ['B'], ['C'], ['D'], ['E'], ['F'], ['G']])
168+
[('A',), ('B',), ('C',), ('D',), ('E',), ('F',), ('G',)])
169169

170170
with self.assertRaises(TypeError): # Too few arguments
171171
list(batched('ABCDEFG'))
@@ -188,8 +188,8 @@ def test_batched(self):
188188
with self.subTest(s=s, n=n, batches=batches):
189189
# Order is preserved and no data is lost
190190
self.assertEqual(''.join(chain(*batches)), s)
191-
# Each batch is an exact list
192-
self.assertTrue(all(type(batch) is list for batch in batches))
191+
# Each batch is an exact tuple
192+
self.assertTrue(all(type(batch) is tuple for batch in batches))
193193
# All but the last batch is of size n
194194
if batches:
195195
last_batch = batches.pop()
@@ -1809,12 +1809,12 @@ class TestPurePythonRoughEquivalents(unittest.TestCase):
18091809

18101810
def test_batched_recipe(self):
18111811
def batched_recipe(iterable, n):
1812-
"Batch data into lists of length n. The last batch may be shorter."
1812+
"Batch data into tuples of length n. The last batch may be shorter."
18131813
# batched('ABCDEFG', 3) --> ABC DEF G
18141814
if n < 1:
18151815
raise ValueError('n must be at least one')
18161816
it = iter(iterable)
1817-
while (batch := list(islice(it, n))):
1817+
while (batch := tuple(islice(it, n))):
18181818
yield batch
18191819

18201820
for iterable, n in product(
@@ -2087,7 +2087,7 @@ def test_accumulate(self):
20872087

20882088
def test_batched(self):
20892089
s = 'abcde'
2090-
r = [['a', 'b'], ['c', 'd'], ['e']]
2090+
r = [('a', 'b'), ('c', 'd'), ('e',)]
20912091
n = 2
20922092
for g in (G, I, Ig, L, R):
20932093
with self.subTest(g=g):

Modules/clinic/itertoolsmodule.c.h

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/itertoolsmodule.c

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@ static PyTypeObject pairwise_type;
5656
/* batched object ************************************************************/
5757

5858
/* Note: The built-in zip() function includes a "strict" argument
59-
that is needed because that function can silently truncate data
60-
and there is no easy way for a user to detect that condition.
61-
The same reasoning does not apply to batched() which never drops
62-
data. Instead, it produces a shorter list which can be handled
63-
as the user sees fit.
59+
that was needed because that function would silently truncate data,
60+
and there was no easy way for a user to detect the data loss.
61+
The same reasoning does not apply to batched() which never drops data.
62+
Instead, batched() produces a shorter tuple which can be handled
63+
as the user sees fit. If requested, it would be reasonable to add
64+
"fillvalue" support which had demonstrated value in zip_longest().
65+
For now, the API is kept simple and clean.
6466
*/
6567

6668
typedef struct {
@@ -74,25 +76,25 @@ typedef struct {
7476
itertools.batched.__new__ as batched_new
7577
iterable: object
7678
n: Py_ssize_t
77-
Batch data into lists of length n. The last batch may be shorter than n.
79+
Batch data into tuples of length n. The last batch may be shorter than n.
7880
79-
Loops over the input iterable and accumulates data into lists
81+
Loops over the input iterable and accumulates data into tuples
8082
up to size n. The input is consumed lazily, just enough to
81-
fill a list. The result is yielded as soon as a batch is full
83+
fill a batch. The result is yielded as soon as a batch is full
8284
or when the input iterable is exhausted.
8385
8486
>>> for batch in batched('ABCDEFG', 3):
8587
... print(batch)
8688
...
87-
['A', 'B', 'C']
88-
['D', 'E', 'F']
89-
['G']
89+
('A', 'B', 'C')
90+
('D', 'E', 'F')
91+
('G',)
9092
9193
[clinic start generated code]*/
9294

9395
static PyObject *
9496
batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n)
95-
/*[clinic end generated code: output=7ebc954d655371b6 input=f28fd12cb52365f0]*/
97+
/*[clinic end generated code: output=7ebc954d655371b6 input=ffd70726927c5129]*/
9698
{
9799
PyObject *it;
98100
batchedobject *bo;
@@ -150,12 +152,12 @@ batched_next(batchedobject *bo)
150152
if (it == NULL) {
151153
return NULL;
152154
}
153-
result = PyList_New(n);
155+
result = PyTuple_New(n);
154156
if (result == NULL) {
155157
return NULL;
156158
}
157159
iternextfunc iternext = *Py_TYPE(it)->tp_iternext;
158-
PyObject **items = _PyList_ITEMS(result);
160+
PyObject **items = _PyTuple_ITEMS(result);
159161
for (i=0 ; i < n ; i++) {
160162
item = iternext(it);
161163
if (item == NULL) {

0 commit comments

Comments
 (0)
0