From 2b57bd0caccabab0460bf243320d76635c5f1d7f Mon Sep 17 00:00:00 2001 From: sayandipdutta Date: Thu, 24 Oct 2024 13:48:45 +0530 Subject: [PATCH 01/14] allow initial as keyword, update docs and news --- Doc/library/functools.rst | 6 +++-- Lib/functools.py | 4 ++-- Lib/test/test_functools.py | 23 +++++++++++++++++++ Misc/ACKS | 1 + ...-10-24-13-40-20.gh-issue-126916.MAgz6D.rst | 2 ++ Modules/_functoolsmodule.c | 13 +++++++---- 6 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-24-13-40-20.gh-issue-126916.MAgz6D.rst diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index e26a2226aa947a..1fe195fe1f06e6 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -453,7 +453,7 @@ The :mod:`functools` module defines the following functions: .. versionadded:: 3.4 -.. function:: reduce(function, iterable[, initial], /) +.. function:: reduce(function, iterable, /[, initial]) Apply *function* of two arguments cumulatively to the items of *iterable*, from left to right, so as to reduce the iterable to a single value. For example, @@ -468,7 +468,7 @@ The :mod:`functools` module defines the following functions: initial_missing = object() - def reduce(function, iterable, initial=initial_missing, /): + def reduce(function, iterable, /, initial=initial_missing): it = iter(iterable) if initial is initial_missing: value = next(it) @@ -480,6 +480,8 @@ The :mod:`functools` module defines the following functions: See :func:`itertools.accumulate` for an iterator that yields all intermediate values. + .. versionchanged:: 3.14 + *initial* is now supported as a keyword argument. .. decorator:: singledispatch diff --git a/Lib/functools.py b/Lib/functools.py index 9d53d3601559b2..8ac911d8306fd8 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -234,9 +234,9 @@ def __ge__(self, other): _initial_missing = object() -def reduce(function, sequence, initial=_initial_missing): +def reduce(function, sequence, /, initial=_initial_missing): """ - reduce(function, iterable[, initial], /) -> value + reduce(function, iterable, /[, initial]) -> value Apply a function of two arguments cumulatively to the items of a sequence or iterable, from left to right, so as to reduce the iterable to a single diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index bdaa9a7ec4f020..8ffb0e9cb9f595 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -1005,6 +1005,29 @@ def __getitem__(self, i): d = {"one": 1, "two": 2, "three": 3} self.assertEqual(self.reduce(add, d), "".join(d.keys())) + # test correctness of keyword usage of `initial` in `reduce` + def test_initial_keyword(self): + def add(x, y): + return x + y + self.assertEqual( + self.reduce(add, ['a', 'b', 'c'], ''), + self.reduce(add, ['a', 'b', 'c'], initial=''), + ) + self.assertEqual( + self.reduce(add, [['a', 'c'], [], ['d', 'w']], []), + self.reduce(add, [['a', 'c'], [], ['d', 'w']], initial=[]), + ) + self.assertEqual( + self.reduce(lambda x, y: x*y, range(2,8), 1), + self.reduce(lambda x, y: x*y, range(2,8), initial=1), + ) + self.assertEqual( + self.reduce(lambda x, y: x*y, range(2,21), 1), + self.reduce(lambda x, y: x*y, range(2,21), initial=1), + ) + self.assertRaises(TypeError, self.reduce, add, [0, 1], initial="") + self.assertEqual(self.reduce(42, "", initial="1"), "1") # func is never called with one item + @unittest.skipUnless(c_functools, 'requires the C _functools module') class TestReduceC(TestReduce, unittest.TestCase): diff --git a/Misc/ACKS b/Misc/ACKS index a1769d9601a2ea..475ade41ee05d3 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -485,6 +485,7 @@ Luke Dunstan Virgil Dupras Bruno Dupuis Andy Dustman +Sayandip Dutta Gary Duzan Eugene Dvurechenski Karmen Dykstra diff --git a/Misc/NEWS.d/next/Library/2024-10-24-13-40-20.gh-issue-126916.MAgz6D.rst b/Misc/NEWS.d/next/Library/2024-10-24-13-40-20.gh-issue-126916.MAgz6D.rst new file mode 100644 index 00000000000000..9b7dd6ddc018ad --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-24-13-40-20.gh-issue-126916.MAgz6D.rst @@ -0,0 +1,2 @@ +Allow the *initial* argument of :func:`functools.reduce` to be a keyword. +Patch by Sayandip Dutta. diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 802b1cf792c555..1625e4c8628d87 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -935,12 +935,16 @@ _functools_cmp_to_key_impl(PyObject *module, PyObject *mycmp) // Not converted to argument clinic, because of `args` in-place modification. // AC will affect performance. static PyObject * -functools_reduce(PyObject *self, PyObject *args) +functools_reduce(PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *seq, *func, *result = NULL, *it; + static char *keywords[] = {"", "", "initial", NULL}; - if (!PyArg_UnpackTuple(args, "reduce", 2, 3, &func, &seq, &result)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|O:reduce", keywords, + &func, &seq, &result)) { return NULL; + } + if (result != NULL) Py_INCREF(result); @@ -1007,7 +1011,7 @@ functools_reduce(PyObject *self, PyObject *args) } PyDoc_STRVAR(functools_reduce_doc, -"reduce(function, iterable[, initial], /) -> value\n\ +"reduce(function, iterable, /[, initial]) -> value\n\ \n\ Apply a function of two arguments cumulatively to the items of a sequence\n\ or iterable, from left to right, so as to reduce the iterable to a single\n\ @@ -1720,7 +1724,8 @@ PyDoc_STRVAR(_functools_doc, "Tools that operate on functions."); static PyMethodDef _functools_methods[] = { - {"reduce", functools_reduce, METH_VARARGS, functools_reduce_doc}, + {"reduce", functools_reduce, METH_VARARGS|METH_KEYWORDS, + functools_reduce_doc}, _FUNCTOOLS_CMP_TO_KEY_METHODDEF {NULL, NULL} /* sentinel */ }; From 47abbd11890008ea3ab08687e0242428745e21db Mon Sep 17 00:00:00 2001 From: Sayandip Dutta Date: Thu, 24 Oct 2024 14:48:36 +0530 Subject: [PATCH 02/14] Apply suggestions from code review Co-authored-by: Sergey B Kirpichev --- Doc/library/functools.rst | 1 + Modules/_functoolsmodule.c | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 1fe195fe1f06e6..69d9d81c848124 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -480,6 +480,7 @@ The :mod:`functools` module defines the following functions: See :func:`itertools.accumulate` for an iterator that yields all intermediate values. + .. versionchanged:: 3.14 *initial* is now supported as a keyword argument. diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 1625e4c8628d87..fe9920cc73d4b0 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -941,7 +941,8 @@ functools_reduce(PyObject *self, PyObject *args, PyObject *kwargs) static char *keywords[] = {"", "", "initial", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|O:reduce", keywords, - &func, &seq, &result)) { + &func, &seq, &result)) + { return NULL; } @@ -1724,7 +1725,7 @@ PyDoc_STRVAR(_functools_doc, "Tools that operate on functions."); static PyMethodDef _functools_methods[] = { - {"reduce", functools_reduce, METH_VARARGS|METH_KEYWORDS, + {"reduce", _PyCFunction_CAST(functools_reduce), METH_VARARGS|METH_KEYWORDS, functools_reduce_doc}, _FUNCTOOLS_CMP_TO_KEY_METHODDEF {NULL, NULL} /* sentinel */ From 2fc884109710c1668cb3dd41c24069f6bf0075f4 Mon Sep 17 00:00:00 2001 From: sayandipdutta Date: Thu, 24 Oct 2024 15:55:47 +0530 Subject: [PATCH 03/14] Use Argument Clinic Taken from patch by Sergey B Kirpichev --- Modules/_functoolsmodule.c | 45 +++++++++--------- Modules/clinic/_functoolsmodule.c.h | 73 ++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 24 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index fe9920cc73d4b0..8faa8ad1acc73e 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -932,19 +932,29 @@ _functools_cmp_to_key_impl(PyObject *module, PyObject *mycmp) /* reduce (used to be a builtin) ********************************************/ -// Not converted to argument clinic, because of `args` in-place modification. -// AC will affect performance. +/*[clinic input] +_functools.reduce + + function as func: object + iterable as seq: object + / + initial as result: object(c_default="NULL") = None + +Apply a function of two arguments cumulatively. + +Apply it to the items of a sequence or iterable, from left to right, so as to +reduce the iterable to a single value. For example, reduce(lambda x, y: x+y, +[1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). If initial is present, it is +placed before the items of the iterable in the calculation, and serves as a +default when the iterable is empty. +[clinic start generated code]*/ + static PyObject * -functools_reduce(PyObject *self, PyObject *args, PyObject *kwargs) +_functools_reduce_impl(PyObject *module, PyObject *func, PyObject *seq, + PyObject *result) +/*[clinic end generated code: output=30d898fe1267c79d input=b7082b8b1473fdc2]*/ { - PyObject *seq, *func, *result = NULL, *it; - static char *keywords[] = {"", "", "initial", NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|O:reduce", keywords, - &func, &seq, &result)) - { - return NULL; - } + PyObject *args, *it; if (result != NULL) Py_INCREF(result); @@ -1011,16 +1021,6 @@ functools_reduce(PyObject *self, PyObject *args, PyObject *kwargs) return NULL; } -PyDoc_STRVAR(functools_reduce_doc, -"reduce(function, iterable, /[, initial]) -> value\n\ -\n\ -Apply a function of two arguments cumulatively to the items of a sequence\n\ -or iterable, from left to right, so as to reduce the iterable to a single\n\ -value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates\n\ -((((1+2)+3)+4)+5). If initial is present, it is placed before the items\n\ -of the iterable in the calculation, and serves as a default when the\n\ -iterable is empty."); - /* lru_cache object **********************************************************/ /* There are four principal algorithmic differences from the pure python version: @@ -1725,8 +1725,7 @@ PyDoc_STRVAR(_functools_doc, "Tools that operate on functions."); static PyMethodDef _functools_methods[] = { - {"reduce", _PyCFunction_CAST(functools_reduce), METH_VARARGS|METH_KEYWORDS, - functools_reduce_doc}, + _FUNCTOOLS_REDUCE_METHODDEF _FUNCTOOLS_CMP_TO_KEY_METHODDEF {NULL, NULL} /* sentinel */ }; diff --git a/Modules/clinic/_functoolsmodule.c.h b/Modules/clinic/_functoolsmodule.c.h index e98984dc4d3a09..2088584f3f080e 100644 --- a/Modules/clinic/_functoolsmodule.c.h +++ b/Modules/clinic/_functoolsmodule.c.h @@ -67,6 +67,77 @@ _functools_cmp_to_key(PyObject *module, PyObject *const *args, Py_ssize_t nargs, return return_value; } +PyDoc_STRVAR(_functools_reduce__doc__, +"reduce($module, function, iterable, /, initial=None)\n" +"--\n" +"\n" +"Apply a function of two arguments cumulatively.\n" +"\n" +"Apply it to the items of a sequence or iterable, from left to right, so as to\n" +"reduce the iterable to a single value. For example, reduce(lambda x, y: x+y,\n" +"[1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). If initial is present, it is\n" +"placed before the items of the iterable in the calculation, and serves as a\n" +"default when the iterable is empty."); + +#define _FUNCTOOLS_REDUCE_METHODDEF \ + {"reduce", _PyCFunction_CAST(_functools_reduce), METH_FASTCALL|METH_KEYWORDS, _functools_reduce__doc__}, + +static PyObject * +_functools_reduce_impl(PyObject *module, PyObject *func, PyObject *seq, + PyObject *result); + +static PyObject * +_functools_reduce(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(initial), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "", "initial", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "reduce", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; + PyObject *func; + PyObject *seq; + PyObject *result = NULL; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 3, 0, argsbuf); + if (!args) { + goto exit; + } + func = args[0]; + seq = args[1]; + if (!noptargs) { + goto skip_optional_pos; + } + result = args[2]; +skip_optional_pos: + return_value = _functools_reduce_impl(module, func, seq, result); + +exit: + return return_value; +} + PyDoc_STRVAR(_functools__lru_cache_wrapper_cache_info__doc__, "cache_info($self, /)\n" "--\n" @@ -114,4 +185,4 @@ _functools__lru_cache_wrapper_cache_clear(PyObject *self, PyObject *Py_UNUSED(ig return return_value; } -/*[clinic end generated code: output=755265bb6d5ea751 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=58c9875c57cbdf51 input=a9049054013a1b77]*/ From 7b795ba1b1a7da5118858f591fc3cf4edbeedd54 Mon Sep 17 00:00:00 2001 From: sayandipdutta Date: Thu, 24 Oct 2024 16:22:29 +0530 Subject: [PATCH 04/14] fix functools.reduce signature for pure python --- Lib/functools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/functools.py b/Lib/functools.py index 8ac911d8306fd8..e5fa8829322e61 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -234,9 +234,9 @@ def __ge__(self, other): _initial_missing = object() -def reduce(function, sequence, /, initial=_initial_missing): +def reduce(function, sequence, initial=_initial_missing): """ - reduce(function, iterable, /[, initial]) -> value + reduce(function, iterable, /, initial=None) -> value Apply a function of two arguments cumulatively to the items of a sequence or iterable, from left to right, so as to reduce the iterable to a single From 11e7d137e1e41e1cf4eecedd3a32e18283336905 Mon Sep 17 00:00:00 2001 From: sayandipdutta Date: Thu, 24 Oct 2024 17:55:46 +0530 Subject: [PATCH 05/14] initial defaults to _functools._initial_missing * Apply patch by Sergey B Kirpichev - fix typo * Update docs --- Doc/library/functools.rst | 2 +- Modules/_functoolsmodule.c | 21 +++++++++++++-------- Modules/clinic/_functoolsmodule.c.h | 18 ++++++++++-------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 69d9d81c848124..00537a0fe46418 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -453,7 +453,7 @@ The :mod:`functools` module defines the following functions: .. versionadded:: 3.4 -.. function:: reduce(function, iterable, /[, initial]) +.. function:: reduce(function, iterable, /, initial=_functools._initial_missing) Apply *function* of two arguments cumulatively to the items of *iterable*, from left to right, so as to reduce the iterable to a single value. For example, diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 8faa8ad1acc73e..e9c7e0924373fb 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -938,21 +938,22 @@ _functools.reduce function as func: object iterable as seq: object / - initial as result: object(c_default="NULL") = None + initial as result: object(c_default="NULL") = _functools._initial_missing -Apply a function of two arguments cumulatively. +Apply a function of two arguments cumulatively to an iterable, from left to right. -Apply it to the items of a sequence or iterable, from left to right, so as to -reduce the iterable to a single value. For example, reduce(lambda x, y: x+y, -[1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). If initial is present, it is -placed before the items of the iterable in the calculation, and serves as a -default when the iterable is empty. +This efficiently reduces the iterable to a single value. If initial is present, +it is placed before the items of the iterable in the calculation, and serves as +a default when the iterable is empty. + +For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) +calculates ((((1+2)+3)+4)+5). [clinic start generated code]*/ static PyObject * _functools_reduce_impl(PyObject *module, PyObject *func, PyObject *seq, PyObject *result) -/*[clinic end generated code: output=30d898fe1267c79d input=b7082b8b1473fdc2]*/ +/*[clinic end generated code: output=30d898fe1267c79d input=40be8069bcbc1a75]*/ { PyObject *args, *it; @@ -1794,6 +1795,10 @@ _functools_exec(PyObject *module) // lru_list_elem is used only in _lru_cache_wrapper. // So we don't expose it in module namespace. + if (PyModule_Add(module, "_initial_missing", _PyObject_New(&PyBaseObject_Type)) < 0) { + return -1; + } + return 0; } diff --git a/Modules/clinic/_functoolsmodule.c.h b/Modules/clinic/_functoolsmodule.c.h index 2088584f3f080e..3f5f1808f7cd44 100644 --- a/Modules/clinic/_functoolsmodule.c.h +++ b/Modules/clinic/_functoolsmodule.c.h @@ -68,16 +68,18 @@ _functools_cmp_to_key(PyObject *module, PyObject *const *args, Py_ssize_t nargs, } PyDoc_STRVAR(_functools_reduce__doc__, -"reduce($module, function, iterable, /, initial=None)\n" +"reduce($module, function, iterable, /,\n" +" initial=_functools._initial_missing)\n" "--\n" "\n" -"Apply a function of two arguments cumulatively.\n" +"Apply a function of two arguments cumulatively to an iterable, from left to right.\n" "\n" -"Apply it to the items of a sequence or iterable, from left to right, so as to\n" -"reduce the iterable to a single value. For example, reduce(lambda x, y: x+y,\n" -"[1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). If initial is present, it is\n" -"placed before the items of the iterable in the calculation, and serves as a\n" -"default when the iterable is empty."); +"This efficiently reduce the iterable to a single value. If initial is present,\n" +"it is placed before the items of the iterable in the calculation, and serves as\n" +"a default when the iterable is empty.\n" +"\n" +"For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])\n" +"calculates ((((1+2)+3)+4)+5)."); #define _FUNCTOOLS_REDUCE_METHODDEF \ {"reduce", _PyCFunction_CAST(_functools_reduce), METH_FASTCALL|METH_KEYWORDS, _functools_reduce__doc__}, @@ -185,4 +187,4 @@ _functools__lru_cache_wrapper_cache_clear(PyObject *self, PyObject *Py_UNUSED(ig return return_value; } -/*[clinic end generated code: output=58c9875c57cbdf51 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f9b9bd6b7e605cb2 input=a9049054013a1b77]*/ From 25c35e30729d85a5f5f2a114a9c9016351e755a0 Mon Sep 17 00:00:00 2001 From: sayandipdutta Date: Thu, 24 Oct 2024 17:59:04 +0530 Subject: [PATCH 06/14] update docstring for python version --- Lib/functools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/functools.py b/Lib/functools.py index e5fa8829322e61..a23f4f6b74d4e7 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -236,7 +236,8 @@ def __ge__(self, other): def reduce(function, sequence, initial=_initial_missing): """ - reduce(function, iterable, /, initial=None) -> value + reduce(function, iterable, /, + initial=_functools._initial_missing) -> value Apply a function of two arguments cumulatively to the items of a sequence or iterable, from left to right, so as to reduce the iterable to a single From d8a5538069656f34a84385ba5ade0549abb4c046 Mon Sep 17 00:00:00 2001 From: Sayandip Dutta Date: Thu, 24 Oct 2024 18:13:10 +0530 Subject: [PATCH 07/14] Apply suggestions from code review Co-authored-by: Peter Bierma --- Doc/library/functools.rst | 2 +- Modules/_functoolsmodule.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 00537a0fe46418..064745719dc69e 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -481,7 +481,7 @@ The :mod:`functools` module defines the following functions: See :func:`itertools.accumulate` for an iterator that yields all intermediate values. - .. versionchanged:: 3.14 + .. versionchanged:: next *initial* is now supported as a keyword argument. .. decorator:: singledispatch diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index e9c7e0924373fb..e76892e812fb65 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -947,7 +947,7 @@ it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) -calculates ((((1+2)+3)+4)+5). +calculates ((((1 + 2) + 3) + 4) + 5). [clinic start generated code]*/ static PyObject * From 84f0a04761046e580f6722474dddc22bfa66be22 Mon Sep 17 00:00:00 2001 From: Sayandip Dutta Date: Thu, 24 Oct 2024 19:42:41 +0530 Subject: [PATCH 08/14] Update Doc/library/functools.rst Co-authored-by: Sergey B Kirpichev --- Doc/library/functools.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 064745719dc69e..a9aceee4170004 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -453,7 +453,7 @@ The :mod:`functools` module defines the following functions: .. versionadded:: 3.4 -.. function:: reduce(function, iterable, /, initial=_functools._initial_missing) +.. function:: reduce(function, iterable, /[, initial]) Apply *function* of two arguments cumulatively to the items of *iterable*, from left to right, so as to reduce the iterable to a single value. For example, From 8b3c2e5f7f291d36bdcd312eec9031f8832893c6 Mon Sep 17 00:00:00 2001 From: sayandipdutta Date: Thu, 24 Oct 2024 18:29:07 +0530 Subject: [PATCH 09/14] review remove private API usage for PyObject_New --- Modules/_functoolsmodule.c | 6 ++++-- Modules/clinic/_functoolsmodule.c.h | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index e76892e812fb65..969ed681876696 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -953,7 +953,7 @@ calculates ((((1 + 2) + 3) + 4) + 5). static PyObject * _functools_reduce_impl(PyObject *module, PyObject *func, PyObject *seq, PyObject *result) -/*[clinic end generated code: output=30d898fe1267c79d input=40be8069bcbc1a75]*/ +/*[clinic end generated code: output=30d898fe1267c79d input=968b8e45b819c0da]*/ { PyObject *args, *it; @@ -1795,7 +1795,9 @@ _functools_exec(PyObject *module) // lru_list_elem is used only in _lru_cache_wrapper. // So we don't expose it in module namespace. - if (PyModule_Add(module, "_initial_missing", _PyObject_New(&PyBaseObject_Type)) < 0) { + if (PyModule_Add(module, "_initial_missing", + PyObject_New(PyObject, &PyBaseObject_Type)) < 0) + { return -1; } diff --git a/Modules/clinic/_functoolsmodule.c.h b/Modules/clinic/_functoolsmodule.c.h index 3f5f1808f7cd44..7cb4497d3b45fa 100644 --- a/Modules/clinic/_functoolsmodule.c.h +++ b/Modules/clinic/_functoolsmodule.c.h @@ -74,12 +74,12 @@ PyDoc_STRVAR(_functools_reduce__doc__, "\n" "Apply a function of two arguments cumulatively to an iterable, from left to right.\n" "\n" -"This efficiently reduce the iterable to a single value. If initial is present,\n" +"This efficiently reduces the iterable to a single value. If initial is present,\n" "it is placed before the items of the iterable in the calculation, and serves as\n" "a default when the iterable is empty.\n" "\n" "For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])\n" -"calculates ((((1+2)+3)+4)+5)."); +"calculates ((((1 + 2) + 3) + 4) + 5)."); #define _FUNCTOOLS_REDUCE_METHODDEF \ {"reduce", _PyCFunction_CAST(_functools_reduce), METH_FASTCALL|METH_KEYWORDS, _functools_reduce__doc__}, @@ -187,4 +187,4 @@ _functools__lru_cache_wrapper_cache_clear(PyObject *self, PyObject *Py_UNUSED(ig return return_value; } -/*[clinic end generated code: output=f9b9bd6b7e605cb2 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=9344464df09b2e41 input=a9049054013a1b77]*/ From 2705ffa575ff12a27c9ff9d8be086df3071903a4 Mon Sep 17 00:00:00 2001 From: sayandipdutta Date: Thu, 24 Oct 2024 23:00:05 +0530 Subject: [PATCH 10/14] inspecting reduce signature unsupported --- Lib/test/test_inspect/test_inspect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 9fa6d23d15f06a..37164e4665a75d 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5697,8 +5697,8 @@ def test_faulthandler_module_has_signatures(self): self._test_module_has_signatures(faulthandler, unsupported_signature=unsupported_signature) def test_functools_module_has_signatures(self): - no_signature = {'reduce'} - self._test_module_has_signatures(functools, no_signature) + unsupported_signature = {"reduce"} + self._test_module_has_signatures(functools, unsupported_signature=unsupported_signature) def test_gc_module_has_signatures(self): import gc From 0d81eb23f156e1e24a8a8f5f9be05ef07ffb8ff3 Mon Sep 17 00:00:00 2001 From: Sayandip Dutta Date: Fri, 25 Oct 2024 10:10:21 +0530 Subject: [PATCH 11/14] Update Lib/functools.py Co-authored-by: Sergey B Kirpichev --- Lib/functools.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/functools.py b/Lib/functools.py index a23f4f6b74d4e7..091f44e5f6d355 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -236,8 +236,7 @@ def __ge__(self, other): def reduce(function, sequence, initial=_initial_missing): """ - reduce(function, iterable, /, - initial=_functools._initial_missing) -> value + reduce(function, iterable, /[, initial]) -> value Apply a function of two arguments cumulatively to the items of a sequence or iterable, from left to right, so as to reduce the iterable to a single From 7e05bc7f60e2d2d5661da1ac2a1d46f40936e304 Mon Sep 17 00:00:00 2001 From: sayandipdutta Date: Sat, 2 Nov 2024 03:16:51 +0530 Subject: [PATCH 12/14] remove _initial_missing --- Modules/_functoolsmodule.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index f9ff9d0fe411ee..5e0cf057dcd1a2 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1795,12 +1795,6 @@ _functools_exec(PyObject *module) // lru_list_elem is used only in _lru_cache_wrapper. // So we don't expose it in module namespace. - if (PyModule_Add(module, "_initial_missing", - PyObject_New(PyObject, &PyBaseObject_Type)) < 0) - { - return -1; - } - return 0; } From f1b5994608746284cce7e4ba776beda5f1a5eeb0 Mon Sep 17 00:00:00 2001 From: Sayandip Dutta Date: Sat, 2 Nov 2024 03:56:07 +0530 Subject: [PATCH 13/14] Update Misc/NEWS.d/next/Library/2024-10-24-13-40-20.gh-issue-126916.MAgz6D.rst Co-authored-by: Erlend E. Aasland --- .../next/Library/2024-10-24-13-40-20.gh-issue-126916.MAgz6D.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-10-24-13-40-20.gh-issue-126916.MAgz6D.rst b/Misc/NEWS.d/next/Library/2024-10-24-13-40-20.gh-issue-126916.MAgz6D.rst index 9b7dd6ddc018ad..cbe2fc166ba6af 100644 --- a/Misc/NEWS.d/next/Library/2024-10-24-13-40-20.gh-issue-126916.MAgz6D.rst +++ b/Misc/NEWS.d/next/Library/2024-10-24-13-40-20.gh-issue-126916.MAgz6D.rst @@ -1,2 +1,2 @@ -Allow the *initial* argument of :func:`functools.reduce` to be a keyword. +Allow the *initial* parameter of :func:`functools.reduce` to be passed as a keyword argument. Patch by Sayandip Dutta. From 7cc052f8e03f8e320a49b11d50ea453d80ab2929 Mon Sep 17 00:00:00 2001 From: sayandipdutta Date: Sat, 2 Nov 2024 12:38:30 +0530 Subject: [PATCH 14/14] update whatsnew entry --- Doc/whatsnew/3.14.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index db32be89cf88ff..561803f7678e4f 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -310,6 +310,10 @@ functools to reserve a place for positional arguments. (Contributed by Dominykas Grigonis in :gh:`119127`.) +* Allow the *initial* parameter of :func:`functools.reduce` to be passed + as a keyword argument. + (Contributed by Sayandip Dutta in :gh:`125916`.) + http ----