Skip to content

Commit

Permalink
leading trailing placeholder lift factored out
Browse files Browse the repository at this point in the history
  • Loading branch information
dg-pb committed Jan 8, 2025
1 parent 7b24727 commit 4f33459
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 93 deletions.
14 changes: 5 additions & 9 deletions Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,15 +310,7 @@ def _partial_prepare_merger(args):
else:
order.append(i)
phcount = j - nargs
if phcount:
if nargs == 1:
i = order[0]
def merger(all_args):
return (all_args[i],)
else:
merger = itemgetter(*order)
else:
merger = None
merger = itemgetter(*order) if phcount else None
return phcount, merger

def _partial_repr(self):
Expand All @@ -342,6 +334,8 @@ class partial:
def __new__(cls, func, /, *args, **keywords):
if not callable(func):
raise TypeError("the first argument must be callable")
if args and args[-1] is Placeholder:
raise TypeError("trailing Placeholders are not allowed")
if isinstance(func, partial):
pto_phcount = func._phcount
tot_args = func.args
Expand Down Expand Up @@ -409,6 +403,8 @@ def __setstate__(self, state):
(namespace is not None and not isinstance(namespace, dict))):
raise TypeError("invalid partial state")

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

args = tuple(args) # just in case it's a subclass
Expand Down
19 changes: 10 additions & 9 deletions Lib/test/test_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,16 +211,11 @@ def foo(bar):
p2.new_attr = 'spam'
self.assertEqual(p2.new_attr, 'spam')

def test_trailing_placeholders(self):
def test_placeholders_trailing_raise(self):
PH = self.module.Placeholder
for args, call_args, expected_args in [
[(PH,), (1,), (1,)],
[(0, PH), (1,), (0, 1)],
[(0, PH, 2, PH, PH), (1, 3, 4), (0, 1, 2, 3, 4)]
]:
actual_args, actual_kwds = self.partial(capture, *args)(*call_args)
self.assertEqual(actual_args, expected_args)
self.assertEqual(actual_kwds, {})
for args in [(PH,), (0, PH), (0, PH, 1, PH, PH, PH)]:
with self.assertRaises(TypeError):
self.partial(capture, *args)

def test_placeholders(self):
PH = self.module.Placeholder
Expand Down Expand Up @@ -373,6 +368,12 @@ def test_setstate(self):
f()
self.assertEqual(f(2), ((2, 1), dict(a=10)))

# Trailing Placeholder error
f = self.partial(signature)
msg_regex = re.escape("trailing Placeholders are not allowed")
with self.assertRaisesRegex(TypeError, f'^{msg_regex}$') as cm:
f.__setstate__((capture, (1, PH), dict(a=10), dict(attr=[])))

def test_setstate_errors(self):
f = self.partial(signature)
self.assertRaises(TypeError, f.__setstate__, (capture, (), {}))
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -3714,6 +3714,11 @@ def test(self: 'anno', x):
((('self', ..., 'anno', 'positional_only'),),
...))

def test_signature_on_fake_partialmethod(self):
def foo(a): pass
foo.__partialmethod__ = 'spam'
self.assertEqual(str(inspect.signature(foo)), '(a)')

def test_signature_on_decorated(self):
def decorator(func):
@functools.wraps(func)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
:func:`functools.partial` now allows trailing placeholders, which are converted to positional-only arguments.
:func:`functools.partialmethod` is simplified by making use of new :func:`functools.partial` placeholder functionality.
136 changes: 62 additions & 74 deletions Modules/_functoolsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,14 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
if (state == NULL) {
return NULL;
}
phold = state->placeholder;

/* Placeholder restrictions */
if (new_nargs && PyTuple_GET_ITEM(args, new_nargs) == phold) {
PyErr_SetString(PyExc_TypeError,
"trailing Placeholders are not allowed");
return NULL;
}

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

phold = state->placeholder;
pto->fn = Py_NewRef(func);
pto->placeholder = phold;

/* process args */
if (new_nargs == 0) {
if (pto_args == NULL) {
pto->args = PyTuple_New(0);
pto->phcount = 0;
}
else {
pto->args = pto_args;
pto->phcount = pto_phcount;
Py_INCREF(pto_args);
assert(PyTuple_Check(pto->args));
}
new_args = PyTuple_GetSlice(args, 1, new_nargs + 1);
if (new_args == NULL) {
Py_DECREF(pto);
return NULL;
}
else {
/* Count placeholders */
Py_ssize_t phcount = 0;
for (Py_ssize_t i = 0; i < new_nargs; i++) {
if (PyTuple_GET_ITEM(args, i + 1) == phold) {
phcount++;
}

/* Count placeholders */
Py_ssize_t phcount = 0;
for (Py_ssize_t i = 0; i < new_nargs - 1; i++) {
if (PyTuple_GET_ITEM(new_args, i) == phold) {
phcount++;
}
if (pto_args == NULL) {
new_args = PyTuple_GetSlice(args, 1, new_nargs + 1);
if (new_args == NULL) {
Py_DECREF(pto);
return NULL;
}
pto->args = new_args;
pto->phcount = phcount;
}
/* merge args with args of `func` which is `partial` */
if (pto_phcount > 0 && new_nargs > 0) {
Py_ssize_t npargs = PyTuple_GET_SIZE(pto_args);
Py_ssize_t tot_nargs = npargs;
if (new_nargs > pto_phcount) {
tot_nargs += new_nargs - pto_phcount;
}
else {
/* merge args with args of `func` which is `partial` */
Py_ssize_t npargs = PyTuple_GET_SIZE(pto_args);
Py_ssize_t tot_nargs = npargs;
if (new_nargs > pto_phcount) {
tot_nargs += new_nargs - pto_phcount;
}
PyObject *tot_args = PyTuple_New(tot_nargs);
PyObject *item;
if (pto_phcount > 0) {
for (Py_ssize_t i = 0, j = 0; i < tot_nargs; ++i) {
if (i < npargs) {
item = PyTuple_GET_ITEM(pto_args, i);
if (j < new_nargs && item == phold) {
item = PyTuple_GET_ITEM(args, j + 1);
j++;
pto_phcount--;
}
}
else {
item = PyTuple_GET_ITEM(args, j + 1);
j++;
}
Py_INCREF(item);
PyTuple_SET_ITEM(tot_args, i, item);
PyObject *item;
PyObject *tot_args = PyTuple_New(tot_nargs);
for (Py_ssize_t i = 0, j = 0; i < tot_nargs; i++) {
if (i < npargs) {
item = PyTuple_GET_ITEM(pto_args, i);
if (j < new_nargs && item == phold) {
item = PyTuple_GET_ITEM(new_args, j);
j++;
pto_phcount--;
}
}
else {
for (Py_ssize_t i = 0; i < npargs; ++i) {
item = PyTuple_GET_ITEM(pto_args, i);
Py_INCREF(item);
PyTuple_SET_ITEM(tot_args, i, item);
}
for (Py_ssize_t i = 0; i < new_nargs; ++i) {
item = PyTuple_GET_ITEM(args, i + 1);
Py_INCREF(item);
PyTuple_SET_ITEM(tot_args, npargs + i, item);
}
item = PyTuple_GET_ITEM(new_args, j);
j++;
}
pto->args = tot_args;
pto->phcount = pto_phcount + phcount;
assert(PyTuple_Check(pto->args));
Py_INCREF(item);
PyTuple_SET_ITEM(tot_args, i, item);
}
pto->args = tot_args;
pto->phcount = pto_phcount + phcount;
Py_DECREF(new_args);
}
else if (pto_args == NULL) {
pto->args = new_args;
pto->phcount = phcount;
}
else {
pto->args = PySequence_Concat(pto_args, new_args);
pto->phcount = pto_phcount + phcount;
Py_DECREF(new_args);
if (pto->args == NULL) {
Py_DECREF(pto);
return NULL;
}
assert(PyTuple_Check(pto->args));
}

/* process keywords */
if (pto_kw == NULL || PyDict_GET_SIZE(pto_kw) == 0) {
if (kw == NULL) {
pto->kw = PyDict_New();
Expand Down Expand Up @@ -414,12 +402,6 @@ partial_vectorcall(PyObject *self, PyObject *const *args,
pto_args, pto_nargs, NULL);
}

/* Fast path if all Placeholders */
if (pto_nargs == pto_phcount) {
return _PyObject_VectorcallTstate(tstate, pto->fn,
args, nargs, kwnames);
}

/* Fast path using PY_VECTORCALL_ARGUMENTS_OFFSET to prepend a single
* positional argument */
if (pto_nargs == 1 && (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET)) {
Expand Down Expand Up @@ -711,8 +693,14 @@ partial_setstate(PyObject *self, PyObject *state)
return NULL;
}

/* Count placeholders */
Py_ssize_t nargs = PyTuple_GET_SIZE(fnargs);
if (nargs && PyTuple_GET_ITEM(fnargs, nargs - 1) == pto->placeholder) {
PyErr_SetString(PyExc_TypeError,
"trailing Placeholders are not allowed");
return NULL;
}

/* Count placeholders */
Py_ssize_t phcount = 0;
for (Py_ssize_t i = 0; i < nargs - 1; i++) {
if (PyTuple_GET_ITEM(fnargs, i) == pto->placeholder) {
Expand Down

0 comments on commit 4f33459

Please sign in to comment.