Skip to content

Commit 5a0209f

Browse files
authored
GH-100242: bring functools.py partial implementation more in line with C code (GH-100244)
in partial.__new__, before checking for the existence of the attribute 'func', first check whether the argument is an instance of partial.
1 parent 8e36cb7 commit 5a0209f

File tree

4 files changed

+30
-5
lines changed

4 files changed

+30
-5
lines changed

Lib/functools.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ def __new__(cls, func, /, *args, **keywords):
285285
if not callable(func):
286286
raise TypeError("the first argument must be callable")
287287

288-
if hasattr(func, "func"):
288+
if isinstance(func, partial):
289289
args = func.args + args
290290
keywords = {**func.keywords, **keywords}
291291
func = func.func

Lib/test/test_functools.py

+13
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,19 @@ def test_nested_optimization(self):
185185
flat = partial(signature, 'asdf', bar=True)
186186
self.assertEqual(signature(nested), signature(flat))
187187

188+
def test_nested_optimization_bug(self):
189+
partial = self.partial
190+
class Builder:
191+
def __call__(self, tag, *children, **attrib):
192+
return (tag, children, attrib)
193+
194+
def __getattr__(self, tag):
195+
return partial(self, tag)
196+
197+
B = Builder()
198+
m = B.m
199+
assert m(1, 2, a=2) == ('m', (1, 2), dict(a=2))
200+
188201
def test_nested_partial_with_attribute(self):
189202
# see issue 25137
190203
partial = self.partial
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Bring pure Python implementation ``functools.partial.__new__`` more in line
2+
with the C-implementation by not just always checking for the presence of
3+
the attribute ``'func'`` on the first argument of ``partial``. Instead, both
4+
the Python version and the C version perform an ``isinstance(func, partial)``
5+
check on the first argument of ``partial``.

Modules/_functoolsmodule.c

+11-4
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,19 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
7979
return NULL;
8080
}
8181

82+
_functools_state *state = get_functools_state_by_type(type);
83+
if (state == NULL) {
84+
return NULL;
85+
}
86+
8287
pargs = pkw = NULL;
8388
func = PyTuple_GET_ITEM(args, 0);
84-
if (Py_TYPE(func)->tp_call == (ternaryfunc)partial_call) {
85-
// The type of "func" might not be exactly the same type object
86-
// as "type", but if it is called using partial_call, it must have the
87-
// same memory layout (fn, args and kw members).
89+
90+
int res = PyObject_TypeCheck(func, state->partial_type);
91+
if (res == -1) {
92+
return NULL;
93+
}
94+
if (res == 1) {
8895
// We can use its underlying function directly and merge the arguments.
8996
partialobject *part = (partialobject *)func;
9097
if (part->dict == NULL) {

0 commit comments

Comments
 (0)