Skip to content

Commit 5fe5051

Browse files
committed
gh-149083: Make functools.Placeholder a sentinel
1 parent 29a92ab commit 5fe5051

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:: 3.15
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
@@ -269,30 +269,7 @@ def reduce(function, sequence, initial=_initial_missing):
269269
### partial() argument application
270270
################################################################################
271271

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

297274
def _partial_prepare_merger(args):
298275
if not args:
@@ -435,7 +412,7 @@ def __setstate__(self, state):
435412

436413

437414
try:
438-
from _functools import partial, Placeholder, _PlaceholderType
415+
from _functools import partial, Placeholder
439416
except ImportError:
440417
pass
441418

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;
@@ -1927,24 +1855,15 @@ _functools_exec(PyObject *module)
19271855
return -1;
19281856
}
19291857

1930-
state->placeholder_type = (PyTypeObject *)PyType_FromModuleAndSpec(module,
1931-
&placeholder_type_spec, NULL);
1932-
if (state->placeholder_type == NULL) {
1933-
return -1;
1934-
}
1935-
if (PyModule_AddType(module, state->placeholder_type) < 0) {
1936-
return -1;
1937-
}
1938-
1939-
PyObject *placeholder = PyObject_CallNoArgs((PyObject *)state->placeholder_type);
1858+
PyObject *placeholder = PySentinel_New("Placeholder", "functools");
19401859
if (placeholder == NULL) {
19411860
return -1;
19421861
}
19431862
if (PyModule_AddObjectRef(module, "Placeholder", placeholder) < 0) {
19441863
Py_DECREF(placeholder);
19451864
return -1;
19461865
}
1947-
Py_DECREF(placeholder);
1866+
state->placeholder = placeholder;
19481867

19491868
state->partial_type = (PyTypeObject *)PyType_FromModuleAndSpec(module,
19501869
&partial_type_spec, NULL);
@@ -1990,7 +1909,6 @@ _functools_traverse(PyObject *module, visitproc visit, void *arg)
19901909
{
19911910
_functools_state *state = get_functools_state(module);
19921911
Py_VISIT(state->kwd_mark);
1993-
Py_VISIT(state->placeholder_type);
19941912
Py_VISIT(state->placeholder);
19951913
Py_VISIT(state->partial_type);
19961914
Py_VISIT(state->keyobject_type);
@@ -2003,7 +1921,6 @@ _functools_clear(PyObject *module)
20031921
{
20041922
_functools_state *state = get_functools_state(module);
20051923
Py_CLEAR(state->kwd_mark);
2006-
Py_CLEAR(state->placeholder_type);
20071924
Py_CLEAR(state->placeholder);
20081925
Py_CLEAR(state->partial_type);
20091926
Py_CLEAR(state->keyobject_type);

0 commit comments

Comments
 (0)