[Python-checkins] GH-98363: Have batched() return tuples (GH-100118)

rhettinger webhook-mailer at python.org
Thu Dec 8 16:08:25 EST 2022


https://github.com/python/cpython/commit/35cc0ea736a323119157117d93e5d68d8247e89f
commit: 35cc0ea736a323119157117d93e5d68d8247e89f
branch: main
author: Raymond Hettinger <rhettinger at users.noreply.github.com>
committer: rhettinger <rhettinger at users.noreply.github.com>
date: 2022-12-08T15:08:16-06:00
summary:

GH-98363: Have batched() return tuples (GH-100118)

files:
M Doc/library/itertools.rst
M Lib/test/test_itertools.py
M Modules/clinic/itertoolsmodule.c.h
M Modules/itertoolsmodule.c

diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst
index 0b5978505a96..624d2430ac20 100644
--- a/Doc/library/itertools.rst
+++ b/Doc/library/itertools.rst
@@ -52,7 +52,7 @@ Iterator            Arguments               Results
 Iterator                        Arguments                       Results                                             Example
 ============================    ============================    =================================================   =============================================================
 :func:`accumulate`              p [,func]                       p0, p0+p1, p0+p1+p2, ...                            ``accumulate([1,2,3,4,5]) --> 1 3 6 10 15``
-:func:`batched`                 p, n                            [p0, p1, ..., p_n-1], ...                           ``batched('ABCDEFG', n=3) --> ABC DEF G``
+:func:`batched`                 p, n                            (p0, p1, ..., p_n-1), ...                           ``batched('ABCDEFG', n=3) --> ABC DEF G``
 :func:`chain`                   p, q, ...                       p0, p1, ... plast, q0, q1, ...                      ``chain('ABC', 'DEF') --> A B C D E F``
 :func:`chain.from_iterable`     iterable                        p0, p1, ... plast, q0, q1, ...                      ``chain.from_iterable(['ABC', 'DEF']) --> A B C D E F``
 :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.
 
 .. function:: batched(iterable, n)
 
-   Batch data from the *iterable* into lists of length *n*. The last
+   Batch data from the *iterable* into tuples of length *n*. The last
    batch may be shorter than *n*.
 
-   Loops over the input iterable and accumulates data into lists up to
-   size *n*.  The input is consumed lazily, just enough to fill a list.
+   Loops over the input iterable and accumulates data into tuples up to
+   size *n*.  The input is consumed lazily, just enough to fill a batch.
    The result is yielded as soon as the batch is full or when the input
    iterable is exhausted:
 
@@ -179,14 +179,14 @@ loops that truncate the stream.
       >>> flattened_data = ['roses', 'red', 'violets', 'blue', 'sugar', 'sweet']
       >>> unflattened = list(batched(flattened_data, 2))
       >>> unflattened
-      [['roses', 'red'], ['violets', 'blue'], ['sugar', 'sweet']]
+      [('roses', 'red'), ('violets', 'blue'), ('sugar', 'sweet')]
 
       >>> for batch in batched('ABCDEFG', 3):
       ...     print(batch)
       ...
-      ['A', 'B', 'C']
-      ['D', 'E', 'F']
-      ['G']
+      ('A', 'B', 'C')
+      ('D', 'E', 'F')
+      ('G',)
 
    Roughly equivalent to::
 
@@ -195,7 +195,7 @@ loops that truncate the stream.
           if n < 1:
               raise ValueError('n must be at least one')
           it = iter(iterable)
-          while (batch := list(islice(it, n))):
+          while (batch := tuple(islice(it, n))):
               yield batch
 
    .. versionadded:: 3.12
diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py
index 5f5bcbc7cfb8..b447b6cbab9c 100644
--- a/Lib/test/test_itertools.py
+++ b/Lib/test/test_itertools.py
@@ -161,11 +161,11 @@ def test_accumulate(self):
 
     def test_batched(self):
         self.assertEqual(list(batched('ABCDEFG', 3)),
-                             [['A', 'B', 'C'], ['D', 'E', 'F'], ['G']])
+                             [('A', 'B', 'C'), ('D', 'E', 'F'), ('G',)])
         self.assertEqual(list(batched('ABCDEFG', 2)),
-                             [['A', 'B'], ['C', 'D'], ['E', 'F'], ['G']])
+                             [('A', 'B'), ('C', 'D'), ('E', 'F'), ('G',)])
         self.assertEqual(list(batched('ABCDEFG', 1)),
-                             [['A'], ['B'], ['C'], ['D'], ['E'], ['F'], ['G']])
+                            [('A',), ('B',), ('C',), ('D',), ('E',), ('F',), ('G',)])
 
         with self.assertRaises(TypeError):          # Too few arguments
             list(batched('ABCDEFG'))
@@ -188,8 +188,8 @@ def test_batched(self):
                 with self.subTest(s=s, n=n, batches=batches):
                     # Order is preserved and no data is lost
                     self.assertEqual(''.join(chain(*batches)), s)
-                    # Each batch is an exact list
-                    self.assertTrue(all(type(batch) is list for batch in batches))
+                    # Each batch is an exact tuple
+                    self.assertTrue(all(type(batch) is tuple for batch in batches))
                     # All but the last batch is of size n
                     if batches:
                         last_batch = batches.pop()
@@ -1809,12 +1809,12 @@ class TestPurePythonRoughEquivalents(unittest.TestCase):
 
     def test_batched_recipe(self):
         def batched_recipe(iterable, n):
-            "Batch data into lists of length n. The last batch may be shorter."
+            "Batch data into tuples of length n. The last batch may be shorter."
             # batched('ABCDEFG', 3) --> ABC DEF G
             if n < 1:
                 raise ValueError('n must be at least one')
             it = iter(iterable)
-            while (batch := list(islice(it, n))):
+            while (batch := tuple(islice(it, n))):
                 yield batch
 
         for iterable, n in product(
@@ -2087,7 +2087,7 @@ def test_accumulate(self):
 
     def test_batched(self):
         s = 'abcde'
-        r = [['a', 'b'], ['c', 'd'], ['e']]
+        r = [('a', 'b'), ('c', 'd'), ('e',)]
         n = 2
         for g in (G, I, Ig, L, R):
             with self.subTest(g=g):
diff --git a/Modules/clinic/itertoolsmodule.c.h b/Modules/clinic/itertoolsmodule.c.h
index 17f9ebb24939..287de524e913 100644
--- a/Modules/clinic/itertoolsmodule.c.h
+++ b/Modules/clinic/itertoolsmodule.c.h
@@ -12,19 +12,19 @@ PyDoc_STRVAR(batched_new__doc__,
 "batched(iterable, n)\n"
 "--\n"
 "\n"
-"Batch data into lists of length n. The last batch may be shorter than n.\n"
+"Batch data into tuples of length n. The last batch may be shorter than n.\n"
 "\n"
-"Loops over the input iterable and accumulates data into lists\n"
+"Loops over the input iterable and accumulates data into tuples\n"
 "up to size n.  The input is consumed lazily, just enough to\n"
-"fill a list.  The result is yielded as soon as a batch is full\n"
+"fill a batch.  The result is yielded as soon as a batch is full\n"
 "or when the input iterable is exhausted.\n"
 "\n"
 "    >>> for batch in batched(\'ABCDEFG\', 3):\n"
 "    ...     print(batch)\n"
 "    ...\n"
-"    [\'A\', \'B\', \'C\']\n"
-"    [\'D\', \'E\', \'F\']\n"
-"    [\'G\']");
+"    (\'A\', \'B\', \'C\')\n"
+"    (\'D\', \'E\', \'F\')\n"
+"    (\'G\',)");
 
 static PyObject *
 batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n);
@@ -913,4 +913,4 @@ itertools_count(PyTypeObject *type, PyObject *args, PyObject *kwargs)
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=efea8cd1e647bd17 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=0229ebd72962f130 input=a9049054013a1b77]*/
diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c
index d45000788c53..60ec11c32d01 100644
--- a/Modules/itertoolsmodule.c
+++ b/Modules/itertoolsmodule.c
@@ -56,11 +56,13 @@ static PyTypeObject pairwise_type;
 /* batched object ************************************************************/
 
 /* Note:  The built-in zip() function includes a "strict" argument
-   that is needed because that function can silently truncate data
-   and there is no easy way for a user to detect that condition.
-   The same reasoning does not apply to batched() which never drops
-   data.  Instead, it produces a shorter list which can be handled
-   as the user sees fit.
+   that was needed because that function would silently truncate data,
+   and there was no easy way for a user to detect the data loss.
+   The same reasoning does not apply to batched() which never drops data.
+   Instead, batched() produces a shorter tuple which can be handled
+   as the user sees fit.  If requested, it would be reasonable to add
+   "fillvalue" support which had demonstrated value in zip_longest().
+   For now, the API is kept simple and clean.
  */
 
 typedef struct {
@@ -74,25 +76,25 @@ typedef struct {
 itertools.batched.__new__ as batched_new
     iterable: object
     n: Py_ssize_t
-Batch data into lists of length n. The last batch may be shorter than n.
+Batch data into tuples of length n. The last batch may be shorter than n.
 
-Loops over the input iterable and accumulates data into lists
+Loops over the input iterable and accumulates data into tuples
 up to size n.  The input is consumed lazily, just enough to
-fill a list.  The result is yielded as soon as a batch is full
+fill a batch.  The result is yielded as soon as a batch is full
 or when the input iterable is exhausted.
 
     >>> for batch in batched('ABCDEFG', 3):
     ...     print(batch)
     ...
-    ['A', 'B', 'C']
-    ['D', 'E', 'F']
-    ['G']
+    ('A', 'B', 'C')
+    ('D', 'E', 'F')
+    ('G',)
 
 [clinic start generated code]*/
 
 static PyObject *
 batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n)
-/*[clinic end generated code: output=7ebc954d655371b6 input=f28fd12cb52365f0]*/
+/*[clinic end generated code: output=7ebc954d655371b6 input=ffd70726927c5129]*/
 {
     PyObject *it;
     batchedobject *bo;
@@ -150,12 +152,12 @@ batched_next(batchedobject *bo)
     if (it == NULL) {
         return NULL;
     }
-    result = PyList_New(n);
+    result = PyTuple_New(n);
     if (result == NULL) {
         return NULL;
     }
     iternextfunc iternext = *Py_TYPE(it)->tp_iternext;
-    PyObject **items = _PyList_ITEMS(result);
+    PyObject **items = _PyTuple_ITEMS(result);
     for (i=0 ; i < n ; i++) {
         item = iternext(it);
         if (item == NULL) {



More information about the Python-checkins mailing list