diff --git a/Lib/functools.py b/Lib/functools.py index 9d53d3601559b2..c42f608c38be6c 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -326,6 +326,10 @@ def _partial_new(cls, func, /, *args, **keywords): "or a descriptor") if args and args[-1] is Placeholder: raise TypeError("trailing Placeholders are not allowed") + if keywords: + for v in keywords.values(): + if v is Placeholder: + raise TypeError("keyword Placeholders are not allowed") if isinstance(func, base_cls): pto_phcount = func._phcount tot_args = func.args diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index bdaa9a7ec4f020..5d937424218c2a 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -249,6 +249,12 @@ def test_placeholders_optimization(self): self.assertEqual(p2.args, (PH, 0)) self.assertEqual(p2(1), ((1, 0), {})) + def test_placeholders_kw_restriction(self): + PH = self.module.Placeholder + exc_string = 'keyword Placeholders are not allowed' + with self.assertRaisesRegex(TypeError, exc_string): + self.partial(capture, a=PH) + def test_construct_placeholder_singleton(self): PH = self.module.Placeholder tp = type(PH) diff --git a/Misc/NEWS.d/next/Library/2024-10-09-15-16-09.gh-issue-125028.IOzxPL.rst b/Misc/NEWS.d/next/Library/2024-10-09-15-16-09.gh-issue-125028.IOzxPL.rst new file mode 100644 index 00000000000000..8320d6b6c10a1d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-09-15-16-09.gh-issue-125028.IOzxPL.rst @@ -0,0 +1 @@ +:data:`functools.Placeholder` is not allowed in :func:`functools.partial` keyword vaules. diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 802b1cf792c555..dc5e4229670797 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -280,30 +280,33 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw) assert(PyTuple_Check(pto->args)); } + /* process keywords */ if (pto_kw == NULL || PyDict_GET_SIZE(pto_kw) == 0) { - if (kw == NULL) { - pto->kw = PyDict_New(); - } - else if (Py_REFCNT(kw) == 1) { - pto->kw = Py_NewRef(kw); - } - else { - pto->kw = PyDict_Copy(kw); - } + pto->kw = PyDict_New(); } else { pto->kw = PyDict_Copy(pto_kw); - if (kw != NULL && pto->kw != NULL) { - if (PyDict_Merge(pto->kw, kw, 1) != 0) { - Py_DECREF(pto); - return NULL; - } - } } if (pto->kw == NULL) { Py_DECREF(pto); return NULL; } + if (kw != NULL) { + PyObject *key, *val; + Py_ssize_t pos = 0; + while (PyDict_Next(kw, &pos, &key, &val)) { + if (val == phold) { + Py_DECREF(pto); + PyErr_SetString(PyExc_TypeError, + "keyword Placeholders are not allowed"); + return NULL; + } + if (PyDict_SetItem(pto->kw, key, val)) { + Py_DECREF(pto); + return NULL; + } + } + } partial_setvectorcall(pto); return (PyObject *)pto;