8000 gh-125028: Prohibit placeholders in partial keywords (GH-126062) · python/cpython@afed5f8 · GitHub
[go: up one dir, main page]

Skip to content

Commit afed5f8

Browse files
authored
gh-125028: Prohibit placeholders in partial keywords (GH-126062)
1 parent 4fcd377 commit afed5f8

File tree

5 files changed

+37
-2
lines changed

5 files changed

+37
-2
lines changed

Doc/library/functools.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,7 @@ The :mod:`functools` module defines the following functions:
403403
>>> remove_first_dear(message)
404404
'Hello, dear world!'
405405

406-
:data:`!Placeholder` has no special treatment when used in a keyword
407-
argument to :func:`!partial`.
406+
:data:`!Placeholder` cannot be passed to :func:`!partial` as a keyword argument.
408407

409408
.. versionchanged:: 3.14
410409
Added support for :data:`Placeholder` in positional arguments.

Lib/functools.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,9 @@ def _partial_new(cls, func, /, *args, **keywords):
323323
"or a descriptor")
324324
if args and args[-1] is Placeholder:
325325
raise TypeError("trailing Placeholders are not allowed")
326+
for value in keywords.values():
327+
if value is Placeholder:
328+
raise TypeError("Placeholder cannot be passed as a keyword argument")
326329
if isinstance(func, base_cls):
327330
pto_phcount = func._phcount
328331
tot_args = func.args

Lib/test/test_functools.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import contextlib
2222
from inspect import Signature
2323

24+
from test.support import ALWAYS_EQ
2425
from test.support import import_helper
2526
from test.support import threading_helper
2627
from test.support import cpython_only
@@ -244,6 +245,13 @@ def test_placeholders(self):
244245
actual_args, actual_kwds = p('x', 'y')
245246
self.assertEqual(actual_args, ('x', 0, 'y', 1))
246247
self.assertEqual(actual_kwds, {})
248+
# Checks via `is` and not `eq`
249+
# thus ALWAYS_EQ isn't treated as Placeholder
250+
p = self.partial(capture, ALWAYS_EQ)
251+
actual_args, actual_kwds = p()
252+
self.assertEqual(len(actual_args), 1)
253+
self.assertIs(actual_args[0], ALWAYS_EQ)
254+
self.assertEqual(actual_kwds, {})
247255

248256
def test_placeholders_optimization(self):
249257
PH = self.module.Placeholder
@@ -260,6 +268,17 @@ def test_placeholders_optimization(self):
260268
self.assertEqual(p2.args, (PH, 0))
261269
self.assertEqual(p2(1), ((1, 0), {}))
262270

271+
def test_placeholders_kw_restriction(self):
272+
PH = self.module.Placeholder
273+
with self.assertRaisesRegex(TypeError, "Placeholder"):
274+
self.partial(capture, a=PH)
275+
# Passes, as checks via `is` and not `eq`
276+
p = self.partial(capture, a=ALWAYS_EQ)
277+
actual_args, actual_kwds = p()
278+
self.assertEqual(actual_args, ())
279+
self.assertEqual(len(actual_kwds), 1)
280+
self.assertIs(actual_kwds['a'], ALWAYS_EQ)
281+
263282
def test_construct_placeholder_singleton(self):
264283
PH = self.module.Placeholder
265284
tp = type(PH)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:data:`functools.Placeholder` cannot be passed to :func:`functools.partial` as a keyword argument.

Modules/_functoolsmodule.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,19 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
196196
return NULL;
197197
}
198198

199+
/* keyword Placeholder prohibition */
200+
if (kw != NULL) {
201+
PyObject *key, *val;
202+
Py_ssize_t pos = 0;
203+
while (PyDict_Next(kw, &pos, &key, &val)) {
204+
if (val == phold) {
205+
PyErr_SetString(PyExc_TypeError,
206+
"Placeholder cannot be passed as a keyword argument");
207+
return NULL;
208+
}
209+
}
210+
}
211+
199212
/* check wrapped function / object */
200213
pto_args = pto_kw = NULL;
201214
int res = PyObject_TypeCheck(func, state->partial_type);

0 commit comments

Comments
 (0)
0