Skip to content

Commit ee074d1

Browse files
gh-149083: Make functools.Placeholder a sentinel (#149088)
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent aa4f541 commit ee074d1

5 files changed

Lines changed: 11 additions & 115 deletions

File tree

‎Doc/library/functools.rst‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,9 @@ The :mod:`!functools` module defines the following functions:
339339

340340
.. versionadded:: 3.14
341341

342+
.. versionchanged:: next
343+
``Placeholder`` is now a :class:`sentinel`.
344+
342345
.. function:: partial(func, /, *args, **keywords)
343346

344347
Return a new :ref:`partial object<partial-objects>` which when called

‎Lib/functools.py‎

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -274,30 +274,7 @@ def reduce(function, sequence, /, initial=_initial_missing):
274274
### partial() argument application
275275
################################################################################
276276

277-
278-
class _PlaceholderType:
279-
"""The type of the Placeholder singleton.
280-
281-
Used as a placeholder for partial arguments.
282-
"""
283-
__instance = None
284-
__slots__ = ()
285-
286-
def __init_subclass__(cls, *args, **kwargs):
287-
raise TypeError(f"type '{cls.__name__}' is not an acceptable base type")
288-
289-
def __new__(cls):
290-
if cls.__instance is None:
291-
cls.__instance = object.__new__(cls)
292-
return cls.__instance
293-
294-
def __repr__(self):
295-
return 'Placeholder'
296-
297-
def __reduce__(self):
298-
return 'Placeholder'
299-
300-
Placeholder = _PlaceholderType()
277+
Placeholder = sentinel('Placeholder')
301278

302279
def _partial_prepare_merger(args):
303280
if not args:
@@ -440,7 +417,7 @@ def __setstate__(self, state):
440417

441418

442419
try:
443-
from _functools import partial, Placeholder, _PlaceholderType
420+
from _functools import partial, Placeholder
444421
except ImportError:
445422
pass
446423

‎Lib/test/test_functools.py‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,12 +279,9 @@ def test_placeholders_kw_restriction(self):
279279
self.assertEqual(len(actual_kwds), 1)
280280
self.assertIs(actual_kwds['a'], ALWAYS_EQ)
281281

282-
def test_construct_placeholder_singleton(self):
282+
def test_placeholder_is_sentinel(self):
283283
PH = self.module.Placeholder
284-
tp = type(PH)
285-
self.assertIs(tp(), PH)
286-
self.assertRaises(TypeError, tp, 1, 2)
287-
self.assertRaises(TypeError, tp, a=1, b=2)
284+
self.assertIsInstance(PH, sentinel)
288285

289286
def test_repr(self):
290287
args = (object(), object())
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:data:`functools.Placeholder` is now a :class:`sentinel`. It is no longer an
2+
instance of its own singleton type.

‎Modules/_functoolsmodule.c‎

Lines changed: 2 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ class _functools._lru_cache_wrapper "PyObject *" "&lru_cache_type_spec"
2727
typedef struct _functools_state {
2828
/* this object is used delimit args and keywords in the cache keys */
2929
PyObject *kwd_mark;
30-
PyTypeObject *placeholder_type;
3130
PyObject *placeholder; // strong reference (singleton)
3231
PyTypeObject *partial_type;
3332
PyTypeObject *keyobject_type;
@@ -55,77 +54,6 @@ typedef struct {
5554
static inline _functools_state *
5655
get_functools_state_by_type(PyTypeObject *type);
5756

58-
PyDoc_STRVAR(placeholder_doc,
59-
"The type of the Placeholder singleton.\n\n"
60-
"Used as a placeholder for partial arguments.");
61-
62-
static PyObject *
63-
placeholder_repr(PyObject *op)
64-
{
65-
return PyUnicode_FromString("Placeholder");
66-
}
67-
68-
static PyObject *
69-
placeholder_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
70-
{
71-
return PyUnicode_FromString("Placeholder");
72-
}
73-
74-
static PyMethodDef placeholder_methods[] = {
75-
{"__reduce__", placeholder_reduce, METH_NOARGS, NULL},
76-
{NULL, NULL}
77-
};
78-
79-
static void
80-
placeholder_dealloc(PyObject* self)
81-
{
82-
PyObject_GC_UnTrack(self);
83-
PyTypeObject *tp = Py_TYPE(self);
84-
tp->tp_free((PyObject*)self);
85-
Py_DECREF(tp);
86-
}
87-
88-
static PyObject *
89-
placeholder_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
90-
{
91-
if (PyTuple_GET_SIZE(args) || (kwargs && PyDict_GET_SIZE(kwargs))) {
92-
PyErr_SetString(PyExc_TypeError, "PlaceholderType takes no arguments");
93-
return NULL;
94-
}
95-
_functools_state *state = get_functools_state_by_type(type);
96-
if (state->placeholder != NULL) {
97-
return Py_NewRef(state->placeholder);
98-
}
99-
100-
PyObject *placeholder = PyType_GenericNew(type, NULL, NULL);
101-
if (placeholder == NULL) {
102-
return NULL;
103-
}
104-
105-
if (state->placeholder == NULL) {
106-
state->placeholder = Py_NewRef(placeholder);
107-
}
108-
return placeholder;
109-
}
110-
111-
static PyType_Slot placeholder_type_slots[] = {
112-
{Py_tp_dealloc, placeholder_dealloc},
113-
{Py_tp_repr, placeholder_repr},
114-
{Py_tp_doc, (void *)placeholder_doc},
115-
{Py_tp_methods, placeholder_methods},
116-
{Py_tp_new, placeholder_new},
117-
{Py_tp_traverse, _PyObject_VisitType},
118-
{0, 0}
119-
};
120-
121-
static PyType_Spec placeholder_type_spec = {
122-
.name = "functools._PlaceholderType",
123-
.basicsize = sizeof(placeholderobject),
124-
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC,
125-
.slots = placeholder_type_slots
126-
};
127-
128-
12957
typedef struct {
13058
PyObject_HEAD
13159
PyObject *fn;
@@ -1950,24 +1878,15 @@ _functools_exec(PyObject *module)
19501878
return -1;
19511879
}
19521880

1953-
state->placeholder_type = (PyTypeObject *)PyType_FromModuleAndSpec(module,
1954-
&placeholder_type_spec, NULL);
1955-
if (state->placeholder_type == NULL) {
1956-
return -1;
1957-
}
1958-
if (PyModule_AddType(module, state->placeholder_type) < 0) {
1959-
return -1;
1960-
}
1961-
1962-
PyObject *placeholder = PyObject_CallNoArgs((PyObject *)state->placeholder_type);
1881+
PyObject *placeholder = PySentinel_New("Placeholder", "functools", NULL);
19631882
if (placeholder == NULL) {
19641883
return -1;
19651884
}
19661885
if (PyModule_AddObjectRef(module, "Placeholder", placeholder) < 0) {
19671886
Py_DECREF(placeholder);
19681887
return -1;
19691888
}
1970-
Py_DECREF(placeholder);
1889+
state->placeholder = placeholder;
19711890

19721891
state->partial_type = (PyTypeObject *)PyType_FromModuleAndSpec(module,
19731892
&partial_type_spec, NULL);
@@ -2013,7 +1932,6 @@ _functools_traverse(PyObject *module, visitproc visit, void *arg)
20131932
{
20141933
_functools_state *state = get_functools_state(module);
20151934
Py_VISIT(state->kwd_mark);
2016-
Py_VISIT(state->placeholder_type);
20171935
Py_VISIT(state->placeholder);
20181936
Py_VISIT(state->partial_type);
20191937
Py_VISIT(state->keyobject_type);
@@ -2026,7 +1944,6 @@ _functools_clear(PyObject *module)
20261944
{
20271945
_functools_state *state = get_functools_state(module);
20281946
Py_CLEAR(state->kwd_mark);
2029-
Py_CLEAR(state->placeholder_type);
20301947
Py_CLEAR(state->placeholder);
20311948
Py_CLEAR(state->partial_type);
20321949
Py_CLEAR(state->keyobject_type);

0 commit comments

Comments
 (0)