https://github.com/python/cpython/commit/ff5806c78edda1feed61254ac55193772dc9ec48
commit: ff5806c78edda1feed61254ac55193772dc9ec48
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-07-03T09:02:15+03:00
summary:

gh-121027: Make the functools.partial object a method descriptor (GH-121089)

Co-authored-by: d.grigonis <[email protected]>

files:
A Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst
M Doc/whatsnew/3.14.rst
M Lib/functools.py
M Lib/test/test_functools.py
M Lib/test/test_inspect/test_inspect.py
M Modules/_functoolsmodule.c

diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 6ebadd75092fac..9578ba0c9c9657 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -305,6 +305,12 @@ Porting to Python 3.14
 This section lists previously described changes and other bugfixes
 that may require changes to your code.
 
+Changes in the Python API
+-------------------------
+
+* :class:`functools.partial` is now a method descriptor.
+  Wrap it in :func:`staticmethod` if you want to preserve the old behavior.
+  (Contributed by Serhiy Storchaka and Dominykas Grigonis in :gh:`121027`.)
 
 Build Changes
 =============
diff --git a/Lib/functools.py b/Lib/functools.py
index d04957c555295e..a10493f0e25360 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -18,6 +18,7 @@
 from collections import namedtuple
 # import types, weakref  # Deferred to single_dispatch()
 from reprlib import recursive_repr
+from types import MethodType
 from _thread import RLock
 
 # Avoid importing types, so we can speedup import time
@@ -314,12 +315,7 @@ def __repr__(self):
     def __get__(self, obj, objtype=None):
         if obj is None:
             return self
-        import warnings
-        warnings.warn('functools.partial will be a method descriptor in '
-                      'future Python versions; wrap it in staticmethod() '
-                      'if you want to preserve the old behavior',
-                      FutureWarning, 2)
-        return self
+        return MethodType(self, obj)
 
     def __reduce__(self):
         return type(self), (self.func,), (self.func, self.args,
@@ -402,7 +398,7 @@ def _method(cls_or_self, /, *args, **keywords):
     def __get__(self, obj, cls=None):
         get = getattr(self.func, "__get__", None)
         result = None
-        if get is not None and not isinstance(self.func, partial):
+        if get is not None:
             new_func = get(obj, cls)
             if new_func is not self.func:
                 # Assume __get__ returning something new indicates the
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index 1ce0f4d0aea6ee..492a16a8c7ff45 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -405,9 +405,7 @@ class A:
         self.assertEqual(A.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
         self.assertEqual(A.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4}))
         self.assertEqual(A.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
-        with self.assertWarns(FutureWarning) as w:
-            self.assertEqual(a.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
-        self.assertEqual(w.filename, __file__)
+        self.assertEqual(a.meth(3, b=4), ((1, a, 3), {'a': 2, 'b': 4}))
         self.assertEqual(a.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4}))
         self.assertEqual(a.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
 
diff --git a/Lib/test/test_inspect/test_inspect.py 
b/Lib/test/test_inspect/test_inspect.py
index 308c09874fe2ac..d39c3ccdc847bd 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -3868,17 +3868,15 @@ def __init__(self, b):
 
         with self.subTest('partial'):
             class CM(type):
-                __call__ = functools.partial(lambda x, a: (x, a), 2)
+                __call__ = functools.partial(lambda x, a, b: (x, a, b), 2)
             class C(metaclass=CM):
-                def __init__(self, b):
+                def __init__(self, c):
                     pass
 
-            with self.assertWarns(FutureWarning):
-                self.assertEqual(C(1), (2, 1))
-            with self.assertWarns(FutureWarning):
-                self.assertEqual(self.signature(C),
-                                ((('a', ..., ..., "positional_or_keyword"),),
-                                ...))
+            self.assertEqual(C(1), (2, C, 1))
+            self.assertEqual(self.signature(C),
+                            ((('b', ..., ..., "positional_or_keyword"),),
+                            ...))
 
         with self.subTest('partialmethod'):
             class CM(type):
@@ -4024,14 +4022,12 @@ class C:
 
         with self.subTest('partial'):
             class C:
-                __init__ = functools.partial(lambda x, a: None, 2)
+                __init__ = functools.partial(lambda x, a, b: None, 2)
 
-            with self.assertWarns(FutureWarning):
-                C(1)  # does not raise
-            with self.assertWarns(FutureWarning):
-                self.assertEqual(self.signature(C),
-                                ((('a', ..., ..., "positional_or_keyword"),),
-                                ...))
+            C(1)  # does not raise
+            self.assertEqual(self.signature(C),
+                            ((('b', ..., ..., "positional_or_keyword"),),
+                            ...))
 
         with self.subTest('partialmethod'):
             class C:
@@ -4284,15 +4280,13 @@ class C:
 
         with self.subTest('partial'):
             class C:
-                __call__ = functools.partial(lambda x, a: (x, a), 2)
+                __call__ = functools.partial(lambda x, a, b: (x, a, b), 2)
 
             c = C()
-            with self.assertWarns(FutureWarning):
-                self.assertEqual(c(1), (2, 1))
-            with self.assertWarns(FutureWarning):
-                self.assertEqual(self.signature(c),
-                                ((('a', ..., ..., "positional_or_keyword"),),
-                                ...))
+            self.assertEqual(c(1), (2, c, 1))
+            self.assertEqual(self.signature(C()),
+                            ((('b', ..., ..., "positional_or_keyword"),),
+                            ...))
 
         with self.subTest('partialmethod'):
             class C:
diff --git 
a/Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst 
b/Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst
new file mode 100644
index 00000000000000..a450726d9afed9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst
@@ -0,0 +1 @@
+Make the :class:`functools.partial` object a method descriptor.
diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c
index 564c271915959a..64766b474514bf 100644
--- a/Modules/_functoolsmodule.c
+++ b/Modules/_functoolsmodule.c
@@ -203,14 +203,7 @@ partial_descr_get(PyObject *self, PyObject *obj, PyObject 
*type)
     if (obj == Py_None || obj == NULL) {
         return Py_NewRef(self);
     }
-    if (PyErr_WarnEx(PyExc_FutureWarning,
-                     "functools.partial will be a method descriptor in "
-                     "future Python versions; wrap it in staticmethod() "
-                     "if you want to preserve the old behavior", 1) < 0)
-    {
-        return NULL;
-    }
-    return Py_NewRef(self);
+    return PyMethod_New(self, obj);
 }
 
 /* Merging keyword arguments using the vectorcall convention is messy, so

_______________________________________________
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