]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-121027: Make the functools.partial object a method descriptor (GH-121089)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 3 Jul 2024 06:02:15 +0000 (09:02 +0300)
committerGitHub <noreply@github.com>
Wed, 3 Jul 2024 06:02:15 +0000 (09:02 +0300)
Co-authored-by: d.grigonis <dgrigonis@users.noreply.github.com>
Doc/whatsnew/3.14.rst
Lib/functools.py
Lib/test/test_functools.py
Lib/test/test_inspect/test_inspect.py
Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst [new file with mode: 0644]
Modules/_functoolsmodule.c

index 6ebadd75092fac6a49cc4ce7cfc573f13fcbf982..9578ba0c9c965770a2a5f25fde06f7939e246200 100644 (file)
@@ -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
 =============
index d04957c555295e41ed221f856bbf5a5a45e0dd6f..a10493f0e25360c7e965ae02cf37425292cab6f5 100644 (file)
@@ -18,6 +18,7 @@ from abc import get_cache_token
 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 @@ class partial:
     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 @@ class partialmethod(object):
     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
index 1ce0f4d0aea6ee477cbb09a54595c02317081981..492a16a8c7ff4582bfb2a5a513d333bd78ef65f9 100644 (file)
@@ -405,9 +405,7 @@ class TestPartial:
         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}))
 
index 308c09874fe2ac7c8818ca7e4f97c4cd36f215f9..d39c3ccdc847bd095413c22e3ef277cc1c6920a8 100644 (file)
@@ -3868,17 +3868,15 @@ class TestSignatureObject(unittest.TestCase):
 
         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 TestSignatureObject(unittest.TestCase):
 
         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 TestSignatureObject(unittest.TestCase):
 
         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 (file)
index 0000000..a450726
--- /dev/null
@@ -0,0 +1 @@
+Make the :class:`functools.partial` object a method descriptor.
index 564c271915959adbe69d1d9c578ed08eebef4227..64766b474514bf2b7d73d7319a08ba980219bc2d 100644 (file)
@@ -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