From ac0c6e128cb6553585af096c851c488b53a6c952 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 26 Jan 2022 07:46:48 -0800 Subject: [PATCH] bpo-46527: allow calling enumerate(iterable=...) again (GH-30904) --- Lib/test/test_enumerate.py | 18 ++++++- .../2022-01-25-19-34-55.bpo-46527.mQLNPk.rst | 2 + Objects/enumobject.c | 47 +++++++++++++++---- 3 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-01-25-19-34-55.bpo-46527.mQLNPk.rst diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py index 906bfc21a26aed..5cb54cff9b76fd 100644 --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -128,6 +128,18 @@ def test_argumentcheck(self): self.assertRaises(TypeError, self.enum, 'abc', 'a') # wrong type self.assertRaises(TypeError, self.enum, 'abc', 2, 3) # too many arguments + def test_kwargs(self): + self.assertEqual(list(self.enum(iterable=Ig(self.seq))), self.res) + expected = list(self.enum(Ig(self.seq), 0)) + self.assertEqual(list(self.enum(iterable=Ig(self.seq), start=0)), + expected) + self.assertEqual(list(self.enum(start=0, iterable=Ig(self.seq))), + expected) + self.assertRaises(TypeError, self.enum, iterable=[], x=3) + self.assertRaises(TypeError, self.enum, start=0, x=3) + self.assertRaises(TypeError, self.enum, x=0, y=3) + self.assertRaises(TypeError, self.enum, x=0) + @support.cpython_only def test_tuple_reuse(self): # Tests an implementation detail where tuple is reused @@ -266,14 +278,16 @@ def test_basicfunction(self): class TestStart(EnumerateStartTestCase): + def enum(self, iterable, start=11): + return enumerate(iterable, start=start) - enum = lambda self, i: enumerate(i, start=11) seq, res = 'abc', [(11, 'a'), (12, 'b'), (13, 'c')] class TestLongStart(EnumerateStartTestCase): + def enum(self, iterable, start=sys.maxsize + 1): + return enumerate(iterable, start=start) - enum = lambda self, i: enumerate(i, start=sys.maxsize+1) seq, res = 'abc', [(sys.maxsize+1,'a'), (sys.maxsize+2,'b'), (sys.maxsize+3,'c')] diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-01-25-19-34-55.bpo-46527.mQLNPk.rst b/Misc/NEWS.d/next/Core and Builtins/2022-01-25-19-34-55.bpo-46527.mQLNPk.rst new file mode 100644 index 00000000000000..c9fd0ed05e2ae3 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-01-25-19-34-55.bpo-46527.mQLNPk.rst @@ -0,0 +1,2 @@ +Allow passing ``iterable`` as a keyword argument to :func:`enumerate` again. +Patch by Jelle Zijlstra. diff --git a/Objects/enumobject.c b/Objects/enumobject.c index 36f592d7c239cf..828f1f925a0a1d 100644 --- a/Objects/enumobject.c +++ b/Objects/enumobject.c @@ -83,6 +83,18 @@ enum_new_impl(PyTypeObject *type, PyObject *iterable, PyObject *start) return (PyObject *)en; } +static int check_keyword(PyObject *kwnames, int index, + const char *name) +{ + PyObject *kw = PyTuple_GET_ITEM(kwnames, index); + if (!_PyUnicode_EqualToASCIIString(kw, name)) { + PyErr_Format(PyExc_TypeError, + "'%S' is an invalid keyword argument for enumerate()", kw); + return 0; + } + return 1; +} + // TODO: Use AC when bpo-43447 is supported static PyObject * enumerate_vectorcall(PyObject *type, PyObject *const *args, @@ -91,31 +103,46 @@ enumerate_vectorcall(PyObject *type, PyObject *const *args, PyTypeObject *tp = _PyType_CAST(type); Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); Py_ssize_t nkwargs = 0; - if (nargs == 0) { - PyErr_SetString(PyExc_TypeError, - "enumerate() missing required argument 'iterable'"); - return NULL; - } if (kwnames != NULL) { nkwargs = PyTuple_GET_SIZE(kwnames); } + // Manually implement enumerate(iterable, start=...) if (nargs + nkwargs == 2) { if (nkwargs == 1) { - PyObject *kw = PyTuple_GET_ITEM(kwnames, 0); - if (!_PyUnicode_EqualToASCIIString(kw, "start")) { - PyErr_Format(PyExc_TypeError, - "'%S' is an invalid keyword argument for enumerate()", kw); + if (!check_keyword(kwnames, 0, "start")) { + return NULL; + } + } else if (nkwargs == 2) { + PyObject *kw0 = PyTuple_GET_ITEM(kwnames, 0); + if (_PyUnicode_EqualToASCIIString(kw0, "start")) { + if (!check_keyword(kwnames, 1, "iterable")) { + return NULL; + } + return enum_new_impl(tp, args[1], args[0]); + } + if (!check_keyword(kwnames, 0, "iterable") || + !check_keyword(kwnames, 1, "start")) { return NULL; } + } return enum_new_impl(tp, args[0], args[1]); } - if (nargs == 1 && nkwargs == 0) { + if (nargs + nkwargs == 1) { + if (nkwargs == 1 && !check_keyword(kwnames, 0, "iterable")) { + return NULL; + } return enum_new_impl(tp, args[0], NULL); } + if (nargs == 0) { + PyErr_SetString(PyExc_TypeError, + "enumerate() missing required argument 'iterable'"); + return NULL; + } + PyErr_Format(PyExc_TypeError, "enumerate() takes at most 2 arguments (%d given)", nargs + nkwargs); return NULL;