https://github.com/python/cpython/commit/5a0209fc23de113747058858a4d2e5fc8213711e
commit: 5a0209fc23de113747058858a4d2e5fc8213711e
branch: main
author: CF Bolz-Tereick <[email protected]>
committer: ambv <[email protected]>
date: 2024-04-17T15:34:46+02:00
summary:

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.

files:
A Misc/NEWS.d/next/Library/2022-12-14-15-53-38.gh-issue-100242.Ny7VUO.rst
M Lib/functools.py
M Lib/test/test_functools.py
M Modules/_functoolsmodule.c

diff --git a/Lib/functools.py b/Lib/functools.py
index b42b9eaa0a045c..a80e1a6c6a56ac 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -285,7 +285,7 @@ def __new__(cls, func, /, *args, **keywords):
         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
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index c48c399a10c853..ec5f6af5e17842 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -185,6 +185,19 @@ def test_nested_optimization(self):
         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
index 00000000000000..e86059942d52db
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-12-14-15-53-38.gh-issue-100242.Ny7VUO.rst
@@ -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``.
diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c
index 25c0ecde73246d..406fcf0da2f7e4 100644
--- a/Modules/_functoolsmodule.c
+++ b/Modules/_functoolsmodule.c
@@ -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) {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to