gh-125028: Prohibit placeholders in partial keywords (GH-126062)
This commit is contained in:
parent
4fcd377563
commit
afed5f8835
@ -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.
|
||||
|
@ -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
|
||||
|
@ -21,6 +21,7 @@ from weakref import proxy
|
||||
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 @@ class TestPartial:
|
||||
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 @@ class TestPartial:
|
||||
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)
|
||||
|
@ -0,0 +1 @@
|
||||
:data:`functools.Placeholder` cannot be passed to :func:`functools.partial` as a keyword argument.
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user