Skip to content

Commit 4f33459

Browse files
committed
leading trailing placeholder lift factored out
1 parent 7b24727 commit 4f33459

5 files changed

Lines changed: 82 additions & 93 deletions

File tree

Lib/functools.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -310,15 +310,7 @@ def _partial_prepare_merger(args):
310310
else:
311311
order.append(i)
312312
phcount = j - nargs
313-
if phcount:
314-
if nargs == 1:
315-
i = order[0]
316-
def merger(all_args):
317-
return (all_args[i],)
318-
else:
319-
merger = itemgetter(*order)
320-
else:
321-
merger = None
313+
merger = itemgetter(*order) if phcount else None
322314
return phcount, merger
323315

324316
def _partial_repr(self):
@@ -342,6 +334,8 @@ class partial:
342334
def __new__(cls, func, /, *args, **keywords):
343335
if not callable(func):
344336
raise TypeError("the first argument must be callable")
337+
if args and args[-1] is Placeholder:
338+
raise TypeError("trailing Placeholders are not allowed")
345339
if isinstance(func, partial):
346340
pto_phcount = func._phcount
347341
tot_args = func.args
@@ -409,6 +403,8 @@ def __setstate__(self, state):
409403
(namespace is not None and not isinstance(namespace, dict))):
410404
raise TypeError("invalid partial state")
411405

406+
if args and args[-1] is Placeholder:
407+
raise TypeError("trailing Placeholders are not allowed")
412408
phcount, merger = _partial_prepare_merger(args)
413409

414410
args = tuple(args) # just in case it's a subclass

Lib/test/test_functools.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,16 +211,11 @@ def foo(bar):
211211
p2.new_attr = 'spam'
212212
self.assertEqual(p2.new_attr, 'spam')
213213

214-
def test_trailing_placeholders(self):
214+
def test_placeholders_trailing_raise(self):
215215
PH = self.module.Placeholder
216-
for args, call_args, expected_args in [
217-
[(PH,), (1,), (1,)],
218-
[(0, PH), (1,), (0, 1)],
219-
[(0, PH, 2, PH, PH), (1, 3, 4), (0, 1, 2, 3, 4)]
220-
]:
221-
actual_args, actual_kwds = self.partial(capture, *args)(*call_args)
222-
self.assertEqual(actual_args, expected_args)
223-
self.assertEqual(actual_kwds, {})
216+
for args in [(PH,), (0, PH), (0, PH, 1, PH, PH, PH)]:
217+
with self.assertRaises(TypeError):
218+
self.partial(capture, *args)
224219

225220
def test_placeholders(self):
226221
PH = self.module.Placeholder
@@ -373,6 +368,12 @@ def test_setstate(self):
373368
f()
374369
self.assertEqual(f(2), ((2, 1), dict(a=10)))
375370

371+
# Trailing Placeholder error
372+
f = self.partial(signature)
373+
msg_regex = re.escape("trailing Placeholders are not allowed")
374+
with self.assertRaisesRegex(TypeError, f'^{msg_regex}$') as cm:
375+
f.__setstate__((capture, (1, PH), dict(a=10), dict(attr=[])))
376+
376377
def test_setstate_errors(self):
377378
f = self.partial(signature)
378379
self.assertRaises(TypeError, f.__setstate__, (capture, (), {}))

Lib/test/test_inspect/test_inspect.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3714,6 +3714,11 @@ def test(self: 'anno', x):
37143714
((('self', ..., 'anno', 'positional_only'),),
37153715
...))
37163716

3717+
def test_signature_on_fake_partialmethod(self):
3718+
def foo(a): pass
3719+
foo.__partialmethod__ = 'spam'
3720+
self.assertEqual(str(inspect.signature(foo)), '(a)')
3721+
37173722
def test_signature_on_decorated(self):
37183723
def decorator(func):
37193724
@functools.wraps(func)
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
:func:`functools.partial` now allows trailing placeholders, which are converted to positional-only arguments.
21
:func:`functools.partialmethod` is simplified by making use of new :func:`functools.partial` placeholder functionality.

Modules/_functoolsmodule.c

Lines changed: 62 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,14 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
187187
if (state == NULL) {
188188
return NULL;
189189
}
190+
phold = state->placeholder;
191+
192+
/* Placeholder restrictions */
193+
if (new_nargs && PyTuple_GET_ITEM(args, new_nargs) == phold) {
194+
PyErr_SetString(PyExc_TypeError,
195+
"trailing Placeholders are not allowed");
196+
return NULL;
197+
}
190198

191199
/* check wrapped function / object */
192200
pto_args = pto_kw = NULL;
@@ -212,86 +220,66 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
212220
if (pto == NULL)
213221
return NULL;
214222

215-
phold = state->placeholder;
216223
pto->fn = Py_NewRef(func);
217224
pto->placeholder = phold;
218225

219-
/* process args */
220-
if (new_nargs == 0) {
221-
if (pto_args == NULL) {
222-
pto->args = PyTuple_New(0);
223-
pto->phcount = 0;
224-
}
225-
else {
226-
pto->args = pto_args;
227-
pto->phcount = pto_phcount;
228-
Py_INCREF(pto_args);
229-
assert(PyTuple_Check(pto->args));
230-
}
226+
new_args = PyTuple_GetSlice(args, 1, new_nargs + 1);
227+
if (new_args == NULL) {
228+
Py_DECREF(pto);
229+
return NULL;
231230
}
232-
else {
233-
/* Count placeholders */
234-
Py_ssize_t phcount = 0;
235-
for (Py_ssize_t i = 0; i < new_nargs; i++) {
236-
if (PyTuple_GET_ITEM(args, i + 1) == phold) {
237-
phcount++;
238-
}
231+
232+
/* Count placeholders */
233+
Py_ssize_t phcount = 0;
234+
for (Py_ssize_t i = 0; i < new_nargs - 1; i++) {
235+
if (PyTuple_GET_ITEM(new_args, i) == phold) {
236+
phcount++;
239237
}
240-
if (pto_args == NULL) {
241-
new_args = PyTuple_GetSlice(args, 1, new_nargs + 1);
242-
if (new_args == NULL) {
243-
Py_DECREF(pto);
244-
return NULL;
245-
}
246-
pto->args = new_args;
247-
pto->phcount = phcount;
238+
}
239+
/* merge args with args of `func` which is `partial` */
240+
if (pto_phcount > 0 && new_nargs > 0) {
241+
Py_ssize_t npargs = PyTuple_GET_SIZE(pto_args);
242+
Py_ssize_t tot_nargs = npargs;
243+
if (new_nargs > pto_phcount) {
244+
tot_nargs += new_nargs - pto_phcount;
248245
}
249-
else {
250-
/* merge args with args of `func` which is `partial` */
251-
Py_ssize_t npargs = PyTuple_GET_SIZE(pto_args);
252-
Py_ssize_t tot_nargs = npargs;
253-
if (new_nargs > pto_phcount) {
254-
tot_nargs += new_nargs - pto_phcount;
255-
}
256-
PyObject *tot_args = PyTuple_New(tot_nargs);
257-
PyObject *item;
258-
if (pto_phcount > 0) {
259-
for (Py_ssize_t i = 0, j = 0; i < tot_nargs; ++i) {
260-
if (i < npargs) {
261-
item = PyTuple_GET_ITEM(pto_args, i);
262-
if (j < new_nargs && item == phold) {
263-
item = PyTuple_GET_ITEM(args, j + 1);
264-
j++;
265-
pto_phcount--;
266-
}
267-
}
268-
else {
269-
item = PyTuple_GET_ITEM(args, j + 1);
270-
j++;
271-
}
272-
Py_INCREF(item);
273-
PyTuple_SET_ITEM(tot_args, i, item);
246+
PyObject *item;
247+
PyObject *tot_args = PyTuple_New(tot_nargs);
248+
for (Py_ssize_t i = 0, j = 0; i < tot_nargs; i++) {
249+
if (i < npargs) {
250+
item = PyTuple_GET_ITEM(pto_args, i);
251+
if (j < new_nargs && item == phold) {
252+
item = PyTuple_GET_ITEM(new_args, j);
253+
j++;
254+
pto_phcount--;
274255
}
275256
}
276257
else {
277-
for (Py_ssize_t i = 0; i < npargs; ++i) {
278-
item = PyTuple_GET_ITEM(pto_args, i);
279-
Py_INCREF(item);
280-
PyTuple_SET_ITEM(tot_args, i, item);
281-
}
282-
for (Py_ssize_t i = 0; i < new_nargs; ++i) {
283-
item = PyTuple_GET_ITEM(args, i + 1);
284-
Py_INCREF(item);
285-
PyTuple_SET_ITEM(tot_args, npargs + i, item);
286-
}
258+
item = PyTuple_GET_ITEM(new_args, j);
259+
j++;
287260
}
288-
pto->args = tot_args;
289-
pto->phcount = pto_phcount + phcount;
290-
assert(PyTuple_Check(pto->args));
261+
Py_INCREF(item);
262+
PyTuple_SET_ITEM(tot_args, i, item);
291263
}
264+
pto->args = tot_args;
265+
pto->phcount = pto_phcount + phcount;
266+
Py_DECREF(new_args);
267+
}
268+
else if (pto_args == NULL) {
269+
pto->args = new_args;
270+
pto->phcount = phcount;
271+
}
272+
else {
273+
pto->args = PySequence_Concat(pto_args, new_args);
274+
pto->phcount = pto_phcount + phcount;
275+
Py_DECREF(new_args);
276+
if (pto->args == NULL) {
277+
Py_DECREF(pto);
278+
return NULL;
279+
}
280+
assert(PyTuple_Check(pto->args));
292281
}
293282

294-
/* process keywords */
295283
if (pto_kw == NULL || PyDict_GET_SIZE(pto_kw) == 0) {
296284
if (kw == NULL) {
297285
pto->kw = PyDict_New();
@@ -414,12 +402,6 @@ partial_vectorcall(PyObject *self, PyObject *const *args,
414402
pto_args, pto_nargs, NULL);
415403
}
416404

417-
/* Fast path if all Placeholders */
418-
if (pto_nargs == pto_phcount) {
419-
return _PyObject_VectorcallTstate(tstate, pto->fn,
420-
args, nargs, kwnames);
421-
}
422-
423405
/* Fast path using PY_VECTORCALL_ARGUMENTS_OFFSET to prepend a single
424406
* positional argument */
425407
if (pto_nargs == 1 && (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET)) {
@@ -711,8 +693,14 @@ partial_setstate(PyObject *self, PyObject *state)
711693
return NULL;
712694
}
713695

714-
/* Count placeholders */
715696
Py_ssize_t nargs = PyTuple_GET_SIZE(fnargs);
697+
if (nargs && PyTuple_GET_ITEM(fnargs, nargs - 1) == pto->placeholder) {
698+
PyErr_SetString(PyExc_TypeError,
699+
"trailing Placeholders are not allowed");
700+
return NULL;
701+
}
702+
703+
/* Count placeholders */
716704
Py_ssize_t phcount = 0;
717705
for (Py_ssize_t i = 0; i < nargs - 1; i++) {
718706
if (PyTuple_GET_ITEM(fnargs, i) == pto->placeholder) {

0 commit comments

Comments
 (0)