From ccaaf820b6daa3b1ec74531bd958fc137e846e0f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 25 Jan 2022 19:35:15 -0800 Subject: [PATCH 1/3] bpo-46527: allow calling enumerate(iterable=...) again As mentioned in the issue, this fixes a regression in 3.11. The regression was introduced in GH-25154 (bpo-43706). There were already comments there about how this was too much code for a simple change. This makes it even worse. --- Lib/test/test_enumerate.py | 18 +++++++- .../2022-01-25-19-34-55.bpo-46527.mQLNPk.rst | 2 + Objects/enumobject.c | 44 ++++++++++++++++--- 3 files changed, 56 insertions(+), 8 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..9b473f178d29d9 100644 --- a/Objects/enumobject.c +++ b/Objects/enumobject.c @@ -91,15 +91,11 @@ 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 enumerable(iterable, start=...) if (nargs + nkwargs == 2) { if (nkwargs == 1) { PyObject *kw = PyTuple_GET_ITEM(kwnames, 0); @@ -108,14 +104,50 @@ enumerate_vectorcall(PyObject *type, PyObject *const *args, "'%S' is an invalid keyword argument for enumerate()", kw); return NULL; } + } else if (nkwargs == 2) { + PyObject *kw0 = PyTuple_GET_ITEM(kwnames, 0); + PyObject *kw1 = PyTuple_GET_ITEM(kwnames, 1); + if (_PyUnicode_EqualToASCIIString(kw0, "start")) { + if (!_PyUnicode_EqualToASCIIString(kw1, "iterable")) { + PyErr_Format(PyExc_TypeError, + "'%S' is an invalid keyword argument for enumerate()", kw1); + return NULL; + } + return enum_new_impl(tp, args[1], args[0]); + } + if (!_PyUnicode_EqualToASCIIString(kw0, "iterable")) { + PyErr_Format(PyExc_TypeError, + "'%S' is an invalid keyword argument for enumerate()", kw0); + return NULL; + } + if (!_PyUnicode_EqualToASCIIString(kw1, "start")) { + PyErr_Format(PyExc_TypeError, + "'%S' is an invalid keyword argument for enumerate()", kw1); + return NULL; + } + } return enum_new_impl(tp, args[0], args[1]); } - if (nargs == 1 && nkwargs == 0) { + if (nargs + nkwargs == 1) { + if (nkwargs == 1) { + PyObject *kw0 = PyTuple_GET_ITEM(kwnames, 0); + if (!_PyUnicode_EqualToASCIIString(kw0, "iterable")) { + PyErr_Format(PyExc_TypeError, + "'%S' is an invalid keyword argument for enumerate()", kw0); + 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; From 1f2d9566df939580d77c77f64aa78eab57935af3 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 25 Jan 2022 19:46:29 -0800 Subject: [PATCH 2/3] Update Objects/enumobject.c --- Objects/enumobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/enumobject.c b/Objects/enumobject.c index 9b473f178d29d9..6710001f4e8db5 100644 --- a/Objects/enumobject.c +++ b/Objects/enumobject.c @@ -95,7 +95,7 @@ enumerate_vectorcall(PyObject *type, PyObject *const *args, nkwargs = PyTuple_GET_SIZE(kwnames); } - // Manually implement enumerable(iterable, start=...) + // Manually implement enumerate(iterable, start=...) if (nargs + nkwargs == 2) { if (nkwargs == 1) { PyObject *kw = PyTuple_GET_ITEM(kwnames, 0); From f5355f78a46a157a6298abf9921bafa9dcfff193 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 25 Jan 2022 22:09:39 -0800 Subject: [PATCH 3/3] add separate check_keyword() function --- Objects/enumobject.c | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/Objects/enumobject.c b/Objects/enumobject.c index 6710001f4e8db5..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, @@ -98,31 +110,19 @@ enumerate_vectorcall(PyObject *type, PyObject *const *args, // 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); - PyObject *kw1 = PyTuple_GET_ITEM(kwnames, 1); if (_PyUnicode_EqualToASCIIString(kw0, "start")) { - if (!_PyUnicode_EqualToASCIIString(kw1, "iterable")) { - PyErr_Format(PyExc_TypeError, - "'%S' is an invalid keyword argument for enumerate()", kw1); + if (!check_keyword(kwnames, 1, "iterable")) { return NULL; } return enum_new_impl(tp, args[1], args[0]); } - if (!_PyUnicode_EqualToASCIIString(kw0, "iterable")) { - PyErr_Format(PyExc_TypeError, - "'%S' is an invalid keyword argument for enumerate()", kw0); - return NULL; - } - if (!_PyUnicode_EqualToASCIIString(kw1, "start")) { - PyErr_Format(PyExc_TypeError, - "'%S' is an invalid keyword argument for enumerate()", kw1); + if (!check_keyword(kwnames, 0, "iterable") || + !check_keyword(kwnames, 1, "start")) { return NULL; } @@ -131,13 +131,8 @@ enumerate_vectorcall(PyObject *type, PyObject *const *args, } if (nargs + nkwargs == 1) { - if (nkwargs == 1) { - PyObject *kw0 = PyTuple_GET_ITEM(kwnames, 0); - if (!_PyUnicode_EqualToASCIIString(kw0, "iterable")) { - PyErr_Format(PyExc_TypeError, - "'%S' is an invalid keyword argument for enumerate()", kw0); - return NULL; - } + if (nkwargs == 1 && !check_keyword(kwnames, 0, "iterable")) { + return NULL; } return enum_new_impl(tp, args[0], NULL); }