From 5fe50517a2b061479ec197d103c4532c66296335 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 27 Apr 2026 20:29:17 -0700 Subject: [PATCH] gh-149083: Make functools.Placeholder a sentinel --- Doc/library/functools.rst | 3 + Lib/functools.py | 27 +----- Lib/test/test_functools.py | 7 +- ...-04-27-20-30-16.gh-issue-149083.xcmpSO.rst | 2 + Modules/_functoolsmodule.c | 87 +------------------ 5 files changed, 11 insertions(+), 115 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-27-20-30-16.gh-issue-149083.xcmpSO.rst diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 265610db3caabd..747b51fd6ea500 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -339,6 +339,9 @@ The :mod:`!functools` module defines the following functions: .. versionadded:: 3.14 + .. versionchanged:: 3.15 + ``Placeholder`` is now a :class:`sentinel`. + .. function:: partial(func, /, *args, **keywords) Return a new :ref:`partial object` which when called diff --git a/Lib/functools.py b/Lib/functools.py index cd374631f16792..98ad38c55f3417 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -269,30 +269,7 @@ def reduce(function, sequence, initial=_initial_missing): ### partial() argument application ################################################################################ - -class _PlaceholderType: - """The type of the Placeholder singleton. - - Used as a placeholder for partial arguments. - """ - __instance = None - __slots__ = () - - def __init_subclass__(cls, *args, **kwargs): - raise TypeError(f"type '{cls.__name__}' is not an acceptable base type") - - def __new__(cls): - if cls.__instance is None: - cls.__instance = object.__new__(cls) - return cls.__instance - - def __repr__(self): - return 'Placeholder' - - def __reduce__(self): - return 'Placeholder' - -Placeholder = _PlaceholderType() +Placeholder = sentinel('Placeholder') def _partial_prepare_merger(args): if not args: @@ -435,7 +412,7 @@ def __setstate__(self, state): try: - from _functools import partial, Placeholder, _PlaceholderType + from _functools import partial, Placeholder except ImportError: pass diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index a8ee7d119e4bc6..21c11b3998a4bc 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -279,12 +279,9 @@ def test_placeholders_kw_restriction(self): self.assertEqual(len(actual_kwds), 1) self.assertIs(actual_kwds['a'], ALWAYS_EQ) - def test_construct_placeholder_singleton(self): + def test_placeholder_is_sentinel(self): PH = self.module.Placeholder - tp = type(PH) - self.assertIs(tp(), PH) - self.assertRaises(TypeError, tp, 1, 2) - self.assertRaises(TypeError, tp, a=1, b=2) + self.assertIsInstance(PH, sentinel) def test_repr(self): args = (object(), object()) diff --git a/Misc/NEWS.d/next/Library/2026-04-27-20-30-16.gh-issue-149083.xcmpSO.rst b/Misc/NEWS.d/next/Library/2026-04-27-20-30-16.gh-issue-149083.xcmpSO.rst new file mode 100644 index 00000000000000..38b8c904a68e25 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-27-20-30-16.gh-issue-149083.xcmpSO.rst @@ -0,0 +1,2 @@ +:data:`functools.Placeholder` is now a :class:`sentinel`. It is no longer an +instance of its own singleton type. diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 19bdf3d47c2fad..bf5dc4b3ae00a7 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -27,7 +27,6 @@ class _functools._lru_cache_wrapper "PyObject *" "&lru_cache_type_spec" typedef struct _functools_state { /* this object is used delimit args and keywords in the cache keys */ PyObject *kwd_mark; - PyTypeObject *placeholder_type; PyObject *placeholder; // strong reference (singleton) PyTypeObject *partial_type; PyTypeObject *keyobject_type; @@ -55,77 +54,6 @@ typedef struct { static inline _functools_state * get_functools_state_by_type(PyTypeObject *type); -PyDoc_STRVAR(placeholder_doc, -"The type of the Placeholder singleton.\n\n" -"Used as a placeholder for partial arguments."); - -static PyObject * -placeholder_repr(PyObject *op) -{ - return PyUnicode_FromString("Placeholder"); -} - -static PyObject * -placeholder_reduce(PyObject *op, PyObject *Py_UNUSED(ignored)) -{ - return PyUnicode_FromString("Placeholder"); -} - -static PyMethodDef placeholder_methods[] = { - {"__reduce__", placeholder_reduce, METH_NOARGS, NULL}, - {NULL, NULL} -}; - -static void -placeholder_dealloc(PyObject* self) -{ - PyObject_GC_UnTrack(self); - PyTypeObject *tp = Py_TYPE(self); - tp->tp_free((PyObject*)self); - Py_DECREF(tp); -} - -static PyObject * -placeholder_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) -{ - if (PyTuple_GET_SIZE(args) || (kwargs && PyDict_GET_SIZE(kwargs))) { - PyErr_SetString(PyExc_TypeError, "PlaceholderType takes no arguments"); - return NULL; - } - _functools_state *state = get_functools_state_by_type(type); - if (state->placeholder != NULL) { - return Py_NewRef(state->placeholder); - } - - PyObject *placeholder = PyType_GenericNew(type, NULL, NULL); - if (placeholder == NULL) { - return NULL; - } - - if (state->placeholder == NULL) { - state->placeholder = Py_NewRef(placeholder); - } - return placeholder; -} - -static PyType_Slot placeholder_type_slots[] = { - {Py_tp_dealloc, placeholder_dealloc}, - {Py_tp_repr, placeholder_repr}, - {Py_tp_doc, (void *)placeholder_doc}, - {Py_tp_methods, placeholder_methods}, - {Py_tp_new, placeholder_new}, - {Py_tp_traverse, _PyObject_VisitType}, - {0, 0} -}; - -static PyType_Spec placeholder_type_spec = { - .name = "functools._PlaceholderType", - .basicsize = sizeof(placeholderobject), - .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC, - .slots = placeholder_type_slots -}; - - typedef struct { PyObject_HEAD PyObject *fn; @@ -1927,16 +1855,7 @@ _functools_exec(PyObject *module) return -1; } - state->placeholder_type = (PyTypeObject *)PyType_FromModuleAndSpec(module, - &placeholder_type_spec, NULL); - if (state->placeholder_type == NULL) { - return -1; - } - if (PyModule_AddType(module, state->placeholder_type) < 0) { - return -1; - } - - PyObject *placeholder = PyObject_CallNoArgs((PyObject *)state->placeholder_type); + PyObject *placeholder = PySentinel_New("Placeholder", "functools"); if (placeholder == NULL) { return -1; } @@ -1944,7 +1863,7 @@ _functools_exec(PyObject *module) Py_DECREF(placeholder); return -1; } - Py_DECREF(placeholder); + state->placeholder = placeholder; state->partial_type = (PyTypeObject *)PyType_FromModuleAndSpec(module, &partial_type_spec, NULL); @@ -1990,7 +1909,6 @@ _functools_traverse(PyObject *module, visitproc visit, void *arg) { _functools_state *state = get_functools_state(module); Py_VISIT(state->kwd_mark); - Py_VISIT(state->placeholder_type); Py_VISIT(state->placeholder); Py_VISIT(state->partial_type); Py_VISIT(state->keyobject_type); @@ -2003,7 +1921,6 @@ _functools_clear(PyObject *module) { _functools_state *state = get_functools_state(module); Py_CLEAR(state->kwd_mark); - Py_CLEAR(state->placeholder_type); Py_CLEAR(state->placeholder); Py_CLEAR(state->partial_type); Py_CLEAR(state->keyobject_type);