]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-100242: bring functools.py partial implementation more in line with C code (GH...
authorCF Bolz-Tereick <cfbolz@gmx.de>
Wed, 17 Apr 2024 13:34:46 +0000 (15:34 +0200)
committerGitHub <noreply@github.com>
Wed, 17 Apr 2024 13:34:46 +0000 (15:34 +0200)
in partial.__new__, before checking for the existence of the attribute
'func', first check whether the argument is an instance of partial.

Lib/functools.py
Lib/test/test_functools.py
Misc/NEWS.d/next/Library/2022-12-14-15-53-38.gh-issue-100242.Ny7VUO.rst [new file with mode: 0644]
Modules/_functoolsmodule.c

index b42b9eaa0a045c06dddfa6540e14a2570fe1f857..a80e1a6c6a56acc0f41853dfe824a23b2293adf8 100644 (file)
@@ -285,7 +285,7 @@ class partial:
         if not callable(func):
             raise TypeError("the first argument must be callable")
 
-        if hasattr(func, "func"):
+        if isinstance(func, partial):
             args = func.args + args
             keywords = {**func.keywords, **keywords}
             func = func.func
index c48c399a10c853ab07067037a3a5538f3460a8af..ec5f6af5e17842c4636e5ee4f606ecf6ae5a3677 100644 (file)
@@ -185,6 +185,19 @@ class TestPartial:
         flat = partial(signature, 'asdf', bar=True)
         self.assertEqual(signature(nested), signature(flat))
 
+    def test_nested_optimization_bug(self):
+        partial = self.partial
+        class Builder:
+            def __call__(self, tag, *children, **attrib):
+                return (tag, children, attrib)
+
+            def __getattr__(self, tag):
+                return partial(self, tag)
+
+        B = Builder()
+        m = B.m
+        assert m(1, 2, a=2) == ('m', (1, 2), dict(a=2))
+
     def test_nested_partial_with_attribute(self):
         # see issue 25137
         partial = self.partial
diff --git a/Misc/NEWS.d/next/Library/2022-12-14-15-53-38.gh-issue-100242.Ny7VUO.rst b/Misc/NEWS.d/next/Library/2022-12-14-15-53-38.gh-issue-100242.Ny7VUO.rst
new file mode 100644 (file)
index 0000000..e860599
--- /dev/null
@@ -0,0 +1,5 @@
+Bring pure Python implementation ``functools.partial.__new__`` more in line
+with the C-implementation by not just always checking for the presence of
+the attribute ``'func'`` on the first argument of ``partial``. Instead, both
+the Python version and the C version perform an ``isinstance(func, partial)``
+check on the first argument of ``partial``.
index 25c0ecde73246d537af693e9f24fbe9174a4fdc7..406fcf0da2f7e42d36aa760c1de75b5eb5077ef7 100644 (file)
@@ -79,12 +79,19 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
         return NULL;
     }
 
+    _functools_state *state = get_functools_state_by_type(type);
+    if (state == NULL) {
+        return NULL;
+    }
+
     pargs = pkw = NULL;
     func = PyTuple_GET_ITEM(args, 0);
-    if (Py_TYPE(func)->tp_call == (ternaryfunc)partial_call) {
-        // The type of "func" might not be exactly the same type object
-        // as "type", but if it is called using partial_call, it must have the
-        // same memory layout (fn, args and kw members).
+
+    int res = PyObject_TypeCheck(func, state->partial_type);
+    if (res == -1) {
+        return NULL;
+    }
+    if (res == 1) {
         // We can use its underlying function directly and merge the arguments.
         partialobject *part = (partialobject *)func;
         if (part->dict == NULL) {