https://github.com/python/cpython/commit/afed5f88359c73f798ff6f0064e37ac1a1f0759b
commit: afed5f88359c73f798ff6f0064e37ac1a1f0759b
branch: main
author: dgpb <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-05-08T10:53:53+03:00
summary:
gh-125028: Prohibit placeholders in partial keywords (GH-126062)
files:
A Misc/NEWS.d/next/Library/2024-10-28-06-54-22.gh-issue-125028.GEY8Ws.rst
M Doc/library/functools.rst
M Lib/functools.py
M Lib/test/test_functools.py
M Modules/_functoolsmodule.c
diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst
index 3a933dff057bbb..3e75621be6dad3 100644
--- a/Doc/library/functools.rst
+++ b/Doc/library/functools.rst
@@ -403,8 +403,7 @@ The :mod:`functools` module defines the following functions:
>>> remove_first_dear(message)
'Hello, dear world!'
- :data:`!Placeholder` has no special treatment when used in a keyword
- argument to :func:`!partial`.
+ :data:`!Placeholder` cannot be passed to :func:`!partial` as a keyword
argument.
.. versionchanged:: 3.14
Added support for :data:`Placeholder` in positional arguments.
diff --git a/Lib/functools.py b/Lib/functools.py
index 714070c6ac9460..7f0eac3f650209 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -323,6 +323,9 @@ def _partial_new(cls, func, /, *args, **keywords):
"or a descriptor")
if args and args[-1] is Placeholder:
raise TypeError("trailing Placeholders are not allowed")
+ for value in keywords.values():
+ if value is Placeholder:
+ raise TypeError("Placeholder cannot be passed as a keyword
argument")
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 2e794b0fc95a22..f7e09fd771eaf2 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -21,6 +21,7 @@
import contextlib
from inspect import Signature
+from test.support import ALWAYS_EQ
from test.support import import_helper
from test.support import threading_helper
from test.support import cpython_only
@@ -244,6 +245,13 @@ def test_placeholders(self):
actual_args, actual_kwds = p('x', 'y')
self.assertEqual(actual_args, ('x', 0, 'y', 1))
self.assertEqual(actual_kwds, {})
+ # Checks via `is` and not `eq`
+ # thus ALWAYS_EQ isn't treated as Placeholder
+ p = self.partial(capture, ALWAYS_EQ)
+ actual_args, actual_kwds = p()
+ self.assertEqual(len(actual_args), 1)
+ self.assertIs(actual_args[0], ALWAYS_EQ)
+ self.assertEqual(actual_kwds, {})
def test_placeholders_optimization(self):
PH = self.module.Placeholder
@@ -260,6 +268,17 @@ 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
+ with self.assertRaisesRegex(TypeError, "Placeholder"):
+ self.partial(capture, a=PH)
+ # Passes, as checks via `is` and not `eq`
+ p = self.partial(capture, a=ALWAYS_EQ)
+ actual_args, actual_kwds = p()
+ self.assertEqual(actual_args, ())
+ self.assertEqual(len(actual_kwds), 1)
+ self.assertIs(actual_kwds['a'], ALWAYS_EQ)
+
def test_construct_placeholder_singleton(self):
PH = self.module.Placeholder
tp = type(PH)
diff --git
a/Misc/NEWS.d/next/Library/2024-10-28-06-54-22.gh-issue-125028.GEY8Ws.rst
b/Misc/NEWS.d/next/Library/2024-10-28-06-54-22.gh-issue-125028.GEY8Ws.rst
new file mode 100644
index 00000000000000..09ebee4d41b68e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-28-06-54-22.gh-issue-125028.GEY8Ws.rst
@@ -0,0 +1 @@
+:data:`functools.Placeholder` cannot be passed to :func:`functools.partial` as
a keyword argument.
diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c
index e6c454faf4b16f..899eef50ecc600 100644
--- a/Modules/_functoolsmodule.c
+++ b/Modules/_functoolsmodule.c
@@ -196,6 +196,19 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject
*kw)
return NULL;
}
+ /* keyword Placeholder prohibition */
+ if (kw != NULL) {
+ PyObject *key, *val;
+ Py_ssize_t pos = 0;
+ while (PyDict_Next(kw, &pos, &key, &val)) {
+ if (val == phold) {
+ PyErr_SetString(PyExc_TypeError,
+ "Placeholder cannot be passed as a keyword
argument");
+ return NULL;
+ }
+ }
+ }
+
/* check wrapped function / object */
pto_args = pto_kw = NULL;
int res = PyObject_TypeCheck(func, state->partial_type);
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]