https://github.com/python/cpython/commit/ee074d18866d2cd4fa59801162774aaab8fdde8f
commit: ee074d18866d2cd4fa59801162774aaab8fdde8f
branch: main
author: Jelle Zijlstra <[email protected]>
committer: picnixz <[email protected]>
date: 2026-11-01T08:00:23+01:00
summary:

gh-149083: Make functools.Placeholder a sentinel (#149088)

Co-authored-by: Bénédikt Tran <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-04-27-20-30-16.gh-issue-149083.xcmpSO.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 4eeafad3fbd915..1bbe30158749c4 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:: next
+      ``Placeholder`` is now a :class:`sentinel`.
+
 .. function:: partial(func, /, *args, **keywords)
 
    Return a new :ref:`partial object<partial-objects>` which when called
diff --git a/Lib/functools.py b/Lib/functools.py
index 2c08791d4d2651..a6cc155b65a4b0 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -274,30 +274,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:
@@ -440,7 +417,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 6aa7916adc0bb8..7727cba4f55f4f 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 0ee6eeb9b00bc9..75b93d87e90a99 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;
@@ -1950,16 +1878,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", NULL);
     if (placeholder == NULL) {
         return -1;
     }
@@ -1967,7 +1886,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);
@@ -2013,7 +1932,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);
@@ -2026,7 +1944,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);

_______________________________________________
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]

Reply via email to