Skip to content

Commit 32bca19

Browse files
committed
singleton sentinel and reduce
1 parent 14b38ca commit 32bca19

File tree

3 files changed

+135
-50
lines changed

3 files changed

+135
-50
lines changed

Lib/functools.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ def __call__(self, /, *args, **keywords):
333333
if len(args) < placeholder_count:
334334
raise TypeError(
335335
"missing positional arguments in 'partial' call; expected "
336-
f"at least {placeholder_count}, got {len(fargs)}")
336+
f"at least {placeholder_count}, got {len(args)}")
337337
f_args = list(self.args)
338338
j, pos = 0, 0
339339
while j < placeholder_count:

Lib/test/test_functools.py

+20-20
Original file line numberDiff line numberDiff line change
@@ -275,25 +275,25 @@ def test_recursive_repr(self):
275275
name = f"{self.partial.__module__}.{self.partial.__qualname__}"
276276

277277
f = self.partial(capture)
278-
f.__setstate__((f, (), {}, {}))
278+
f.__setstate__((f, (), {}, 0, {}))
279279
try:
280280
self.assertEqual(repr(f), '%s(...)' % (name,))
281281
finally:
282-
f.__setstate__((capture, (), {}, {}))
282+
f.__setstate__((capture, (), {}, 0, {}))
283283

284284
f = self.partial(capture)
285-
f.__setstate__((capture, (f,), {}, {}))
285+
f.__setstate__((capture, (f,), {}, 0, {}))
286286
try:
287287
self.assertEqual(repr(f), '%s(%r, ...)' % (name, capture,))
288288
finally:
289-
f.__setstate__((capture, (), {}, {}))
289+
f.__setstate__((capture, (), {}, 0, {}))
290290

291291
f = self.partial(capture)
292-
f.__setstate__((capture, (), {'a': f}, {}))
292+
f.__setstate__((capture, (), {'a': f}, 0, {}))
293293
try:
294294
self.assertEqual(repr(f), '%s(%r, a=...)' % (name, capture,))
295295
finally:
296-
f.__setstate__((capture, (), {}, {}))
296+
f.__setstate__((capture, (), {}, 0, {}))
297297

298298
def test_pickle(self):
299299
with replaced_module('functools', self.module):
@@ -325,24 +325,24 @@ def test_deepcopy(self):
325325

326326
def test_setstate(self):
327327
f = self.partial(signature)
328-
f.__setstate__((capture, (1,), dict(a=10), dict(attr=[])))
328+
f.__setstate__((capture, (1,), dict(a=10), 0, dict(attr=[])))
329329

330330
self.assertEqual(signature(f),
331331
(capture, (1,), dict(a=10), dict(attr=[])))
332332
self.assertEqual(f(2, b=20), ((1, 2), {'a': 10, 'b': 20}))
333333

334-
f.__setstate__((capture, (1,), dict(a=10), None))
334+
f.__setstate__((capture, (1,), dict(a=10), 0, None))
335335

336336
self.assertEqual(signature(f), (capture, (1,), dict(a=10), {}))
337337
self.assertEqual(f(2, b=20), ((1, 2), {'a': 10, 'b': 20}))
338338

339-
f.__setstate__((capture, (1,), None, None))
339+
f.__setstate__((capture, (1,), None, 0, None))
340340
#self.assertEqual(signature(f), (capture, (1,), {}, {}))
341341
self.assertEqual(f(2, b=20), ((1, 2), {'b': 20}))
342342
self.assertEqual(f(2), ((1, 2), {}))
343343
self.assertEqual(f(), ((1,), {}))
344344

345-
f.__setstate__((capture, (), {}, None))
345+
f.__setstate__((capture, (), {}, 0, None))
346346
self.assertEqual(signature(f), (capture, (), {}, {}))
347347
self.assertEqual(f(2, b=20), ((2,), {'b': 20}))
348348
self.assertEqual(f(2), ((2,), {}))
@@ -360,7 +360,7 @@ def test_setstate_errors(self):
360360

361361
def test_setstate_subclasses(self):
362362
f = self.partial(signature)
363-
f.__setstate__((capture, MyTuple((1,)), MyDict(a=10), None))
363+
f.__setstate__((capture, MyTuple((1,)), MyDict(a=10), 0, None))
364364
s = signature(f)
365365
self.assertEqual(s, (capture, (1,), dict(a=10), {}))
366366
self.assertIs(type(s[1]), tuple)
@@ -370,7 +370,7 @@ def test_setstate_subclasses(self):
370370
self.assertIs(type(r[0]), tuple)
371371
self.assertIs(type(r[1]), dict)
372372

373-
f.__setstate__((capture, BadTuple((1,)), {}, None))
373+
f.__setstate__((capture, BadTuple((1,)), {}, 0, None))
374374
s = signature(f)
375375
self.assertEqual(s, (capture, (1,), {}, {}))
376376
self.assertIs(type(s[1]), tuple)
@@ -381,39 +381,39 @@ def test_setstate_subclasses(self):
381381
def test_recursive_pickle(self):
382382
with replaced_module('functools', self.module):
383383
f = self.partial(capture)
384-
f.__setstate__((f, (), {}, {}))
384+
f.__setstate__((f, (), {}, 0, {}))
385385
try:
386386
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
387387
# gh-117008: Small limit since pickle uses C stack memory
388388
with support.infinite_recursion(100):
389389
with self.assertRaises(RecursionError):
390390
pickle.dumps(f, proto)
391391
finally:
392-
f.__setstate__((capture, (), {}, {}))
392+
f.__setstate__((capture, (), {}, 0, {}))
393393

394394
f = self.partial(capture)
395-
f.__setstate__((capture, (f,), {}, {}))
395+
f.__setstate__((capture, (f,), {}, 0, {}))
396396
try:
397397
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
398398
f_copy = pickle.loads(pickle.dumps(f, proto))
399399
try:
400400
self.assertIs(f_copy.args[0], f_copy)
401401
finally:
402-
f_copy.__setstate__((capture, (), {}, {}))
402+
f_copy.__setstate__((capture, (), {}, 0, {}))
403403
finally:
404-
f.__setstate__((capture, (), {}, {}))
404+
f.__setstate__((capture, (), {}, 0, {}))
405405

406406
f = self.partial(capture)
407-
f.__setstate__((capture, (), {'a': f}, {}))
407+
f.__setstate__((capture, (), {'a': f}, 0, {}))
408408
try:
409409
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
410410
f_copy = pickle.loads(pickle.dumps(f, proto))
411411
try:
412412
self.assertIs(f_copy.keywords['a'], f_copy)
413413
finally:
414-
f_copy.__setstate__((capture, (), {}, {}))
414+
f_copy.__setstate__((capture, (), {}, 0, {}))
415415
finally:
416-
f.__setstate__((capture, (), {}, {}))
416+
f.__setstate__((capture, (), {}, 0, {}))
417417

418418
# Issue 6083: Reference counting bug
419419
def test_setstate_refcount(self):

Modules/_functoolsmodule.c

+114-29
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,118 @@ get_functools_state(PyObject *module)
4343
/* partial object **********************************************************/
4444

4545

46-
typedef struct {
47-
PyObject_HEAD
48-
} placeholderobject;
46+
/*
47+
Placeholder is an object that can be used to signal that positional
48+
argument place is empty when using `partial` class
49+
*/
4950

51+
PyObject*
52+
Py_GetPlaceholder(void);
53+
54+
static PyObject *
55+
placeholder_repr(PyObject *op)
56+
{
57+
return PyUnicode_FromString("Placeholder");
58+
}
5059

51-
PyDoc_STRVAR(placeholder_doc, "placeholder for partial arguments");
60+
static void
61+
placeholder_dealloc(PyObject* placeholder)
62+
{
63+
/* This should never get called, but we also don't want to SEGV if
64+
* we accidentally decref None out of existence. Instead,
65+
* since None is an immortal object, re-set the reference count.
66+
*/
67+
_Py_SetImmortal(placeholder);
68+
}
5269

70+
static PyObject *
71+
placeholder_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
72+
{
73+
if (PyTuple_GET_SIZE(args) || (kwargs && PyDict_GET_SIZE(kwargs))) {
74+
PyErr_SetString(PyExc_TypeError, "PlaceholderType takes no arguments");
75+
return NULL;
76+
}
77+
return Py_GetPlaceholder();
78+
}
5379

54-
static PyType_Slot placeholder_type_slots[] = {
55-
{Py_tp_doc, (void *)placeholder_doc},
56-
{0, 0}
80+
static int
81+
placeholder_bool(PyObject *v)
82+
{
83+
PyErr_SetString(PyExc_TypeError,
84+
"Placeholder should not be used in a boolean context");
85+
return -1;
86+
}
87+
88+
static PyNumberMethods placeholder_as_number = {
89+
.nb_bool = placeholder_bool,
5790
};
5891

59-
static PyType_Spec placeholder_type_spec = {
60-
.name = "functools.Placeholder",
61-
.basicsize = sizeof(placeholderobject),
62-
.flags = Py_TPFLAGS_DEFAULT |
63-
Py_TPFLAGS_IMMUTABLETYPE |
64-
Py_TPFLAGS_DISALLOW_INSTANTIATION,
65-
.slots = placeholder_type_slots
66-
}; // TODO> test: test.support.check_disallow_instantiation
92+
93+
PyDoc_STRVAR(placeholder_doc,
94+
"PlaceholderType()\n"
95+
"--\n\n"
96+
"The type of the Placeholder singleton.");
97+
98+
PyTypeObject _PyPlaceholder_Type = {
99+
PyVarObject_HEAD_INIT(&PyType_Type, 0)
100+
"PlaceholderType",
101+
0,
102+
0,
103+
placeholder_dealloc, /*tp_dealloc*/ /*never called*/
104+
0, /*tp_vectorcall_offset*/
105+
0, /*tp_getattr*/
106+
0, /*tp_setattr*/
107+
0, /*tp_as_async*/
108+
placeholder_repr, /*tp_repr*/
109+
&placeholder_as_number, /*tp_as_number*/
110+
0, /*tp_as_sequence*/
111+
0, /*tp_as_mapping*/
112+
0, /*tp_hash */
113+
0, /*tp_call */
114+
0, /*tp_str */
115+
0, /*tp_getattro */
116+
0, /*tp_setattro */
117+
0, /*tp_as_buffer */
118+
Py_TPFLAGS_DEFAULT, /*tp_flags */
119+
placeholder_doc, /*tp_doc */
120+
0, /*tp_traverse */
121+
0, /*tp_clear */
122+
0, /*tp_richcompare */
123+
0, /*tp_weaklistoffset */
124+
0, /*tp_iter */
125+
0, /*tp_iternext */
126+
0, /*tp_methods */
127+
0, /*tp_members */
128+
0, /*tp_getset */
129+
0, /*tp_base */
130+
0, /*tp_dict */
131+
0, /*tp_descr_get */
132+
0, /*tp_descr_set */
133+
0, /*tp_dictoffset */
134+
0, /*tp_init */
135+
0, /*tp_alloc */
136+
placeholder_new, /*tp_new */
137+
};
138+
139+
PyObject _Py_PlaceholderStruct = _PyObject_HEAD_INIT(&_PyPlaceholder_Type);
140+
141+
#define PY_CONSTANT_PLACEHOLDER 0
142+
143+
static PyObject* constants[] = {
144+
&_Py_PlaceholderStruct, // PY_CONSTANT_PLACEHOLDER
145+
};
146+
147+
PyObject*
148+
Py_GetPlaceholder(void)
149+
{
150+
if (PY_CONSTANT_PLACEHOLDER < Py_ARRAY_LENGTH(constants)) {
151+
return constants[PY_CONSTANT_PLACEHOLDER];
152+
}
153+
else {
154+
PyErr_BadInternalCall();
155+
return NULL;
156+
}
157+
}
67158

68159

69160
typedef struct {
@@ -150,7 +241,7 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
150241
return NULL;
151242
}
152243

153-
pto->placeholder = (PyObject *) state->placeholder_type;
244+
pto->placeholder = Py_GetPlaceholder();
154245
Py_ssize_t nnp = 0;
155246
Py_ssize_t nnargs = PyTuple_GET_SIZE(nargs);
156247
PyObject *item;
@@ -598,18 +689,19 @@ partial_repr(partialobject *pto)
598689
static PyObject *
599690
partial_reduce(partialobject *pto, PyObject *unused)
600691
{
601-
return Py_BuildValue("O(O)(OOOO)", Py_TYPE(pto), pto->fn, pto->fn,
602-
pto->args, pto->kw,
692+
return Py_BuildValue("O(O)(OOOnO)", Py_TYPE(pto), pto->fn, pto->fn,
693+
pto->args, pto->kw, pto->np,
603694
pto->dict ? pto->dict : Py_None);
604695
}
605696

606697
static PyObject *
607698
partial_setstate(partialobject *pto, PyObject *state)
608699
{
609700
PyObject *fn, *fnargs, *kw, *dict;
701+
Py_ssize_t np;
610702

611703
if (!PyTuple_Check(state) ||
612-
!PyArg_ParseTuple(state, "OOOO", &fn, &fnargs, &kw, &dict) ||
704+
!PyArg_ParseTuple(state, "OOOnO", &fn, &fnargs, &kw, &np, &dict) ||
613705
!PyCallable_Check(fn) ||
614706
!PyTuple_Check(fnargs) ||
615707
(kw != Py_None && !PyDict_Check(kw)))
@@ -640,10 +732,10 @@ partial_setstate(partialobject *pto, PyObject *state)
640732
dict = NULL;
641733
else
642734
Py_INCREF(dict);
643-
644735
Py_SETREF(pto->fn, Py_NewRef(fn));
645736
Py_SETREF(pto->args, fnargs);
646737
Py_SETREF(pto->kw, kw);
738+
pto->np = np;
647739
Py_XSETREF(pto->dict, dict);
648740
partial_setvectorcall(pto);
649741
Py_RETURN_NONE;
@@ -1626,6 +1718,7 @@ static PyType_Spec lru_cache_type_spec = {
16261718

16271719
/* module level code ********************************************************/
16281720

1721+
16291722
PyDoc_STRVAR(_functools_doc,
16301723
"Tools that operate on functions.");
16311724

@@ -1644,15 +1737,9 @@ _functools_exec(PyObject *module)
16441737
return -1;
16451738
}
16461739

1647-
state->placeholder_type = (PyTypeObject *)PyType_FromModuleAndSpec(module,
1648-
&placeholder_type_spec, NULL);
1649-
if (state->placeholder_type == NULL) {
1650-
return -1;
1651-
}
1652-
if (PyModule_AddType(module, state->placeholder_type) < 0) {
1740+
if (PyModule_AddObject(module, "Placeholder", Py_GetPlaceholder()) < 0) {
16531741
return -1;
16541742
}
1655-
16561743
state->partial_type = (PyTypeObject *)PyType_FromModuleAndSpec(module,
16571744
&partial_type_spec, NULL);
16581745
if (state->partial_type == NULL) {
@@ -1697,7 +1784,6 @@ _functools_traverse(PyObject *module, visitproc visit, void *arg)
16971784
{
16981785
_functools_state *state = get_functools_state(module);
16991786
Py_VISIT(state->kwd_mark);
1700-
Py_VISIT(state->placeholder_type);
17011787
Py_VISIT(state->partial_type);
17021788
Py_VISIT(state->keyobject_type);
17031789
Py_VISIT(state->lru_list_elem_type);
@@ -1709,7 +1795,6 @@ _functools_clear(PyObject *module)
17091795
{
17101796
_functools_state *state = get_functools_state(module);
17111797
Py_CLEAR(state->kwd_mark);
1712-
Py_CLEAR(state->placeholder_type);
17131798
Py_CLEAR(state->partial_type);
17141799
Py_CLEAR(state->keyobject_type);
17151800
Py_CLEAR(state->lru_list_elem_type);

0 commit comments

Comments
 (0)