summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2024-06-27 12:13:01 (GMT)
committerGitHub <noreply@github.com>2024-06-27 12:13:01 (GMT)
commit49e5740135670e04ae6da7e6f52dbe380655e0f1 (patch)
tree47423e05c4f054ac6c6f1e7b42986ddb65b5b7d9
parentc7d2b2b646f5abdbec501f63ab9b719b3db70a1f (diff)
downloadcpython-49e5740135670e04ae6da7e6f52dbe380655e0f1.zip
cpython-49e5740135670e04ae6da7e6f52dbe380655e0f1.tar.gz
cpython-49e5740135670e04ae6da7e6f52dbe380655e0f1.tar.bz2
[3.13] gh-121027: Add a future warning in functools.partial.__get__ (GH-121086) (#121092)
gh-121027: Add a future warning in functools.partial.__get__ (GH-121086) (cherry picked from commit db96edd6d1a58045196a71aff565743f493b5fbb) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
-rw-r--r--Doc/whatsnew/3.13.rst6
-rw-r--r--Lib/functools.py12
-rw-r--r--Lib/inspect.py8
-rw-r--r--Lib/test/test_functools.py17
-rw-r--r--Lib/test/test_inspect/test_inspect.py31
-rw-r--r--Misc/NEWS.d/next/Library/2024-06-27-13-47-14.gh-issue-121027.jh55EC.rst2
-rw-r--r--Modules/_functoolsmodule.c16
7 files changed, 75 insertions, 17 deletions
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index 971f92c..992d39e 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -2250,6 +2250,12 @@ Changes in the Python API
returned by :meth:`zipfile.ZipFile.open` was changed from ``'r'`` to ``'rb'``.
(Contributed by Serhiy Storchaka in :gh:`115961`.)
+* :class:`functools.partial` now emits a :exc:`FutureWarning` when it is
+ used as a method.
+ Its behavior will be changed in future Python versions.
+ Wrap it in :func:`staticmethod` if you want to preserve the old behavior.
+ (Contributed by Serhiy Storchaka in :gh:`121027`.)
+
.. _pep667-porting-notes-py:
* Calling :func:`locals` in an :term:`optimized scope` now produces an
diff --git a/Lib/functools.py b/Lib/functools.py
index 3d0fd66..d04957c 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -311,6 +311,16 @@ class partial:
args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
return f"{module}.{qualname}({', '.join(args)})"
+ 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
+
def __reduce__(self):
return type(self), (self.func,), (self.func, self.args,
self.keywords or None, self.__dict__ or None)
@@ -392,7 +402,7 @@ class partialmethod(object):
def __get__(self, obj, cls=None):
get = getattr(self.func, "__get__", None)
result = None
- if get is not None:
+ if get is not None and not isinstance(self.func, partial):
new_func = get(obj, cls)
if new_func is not self.func:
# Assume __get__ returning something new indicates the
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 2c82ad5..bf979e8 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -2556,6 +2556,10 @@ def _signature_from_callable(obj, *,
new_params = (first_wrapped_param,) + sig_params
return sig.replace(parameters=new_params)
+ if isinstance(obj, functools.partial):
+ wrapped_sig = _get_signature_of(obj.func)
+ return _signature_get_partial(wrapped_sig, obj)
+
if isfunction(obj) or _signature_is_functionlike(obj):
# If it's a pure Python function, or an object that is duck type
# of a Python function (Cython functions, for instance), then:
@@ -2567,10 +2571,6 @@ def _signature_from_callable(obj, *,
return _signature_from_builtin(sigcls, obj,
skip_bound_arg=skip_bound_arg)
- if isinstance(obj, functools.partial):
- wrapped_sig = _get_signature_of(obj.func)
- return _signature_get_partial(wrapped_sig, obj)
-
if isinstance(obj, type):
# obj is a class or a metaclass
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index 559213f..1ce0f4d 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -395,6 +395,23 @@ class TestPartial:
f = self.partial(object)
self.assertRaises(TypeError, f.__setstate__, BadSequence())
+ def test_partial_as_method(self):
+ class A:
+ meth = self.partial(capture, 1, a=2)
+ cmeth = classmethod(self.partial(capture, 1, a=2))
+ smeth = staticmethod(self.partial(capture, 1, a=2))
+
+ a = 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.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4}))
+ self.assertEqual(a.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
+
+
@unittest.skipUnless(c_functools, 'requires the C _functools module')
class TestPartialC(TestPartial, unittest.TestCase):
if c_functools:
diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py
index 34739b6..5d0f328 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -3873,10 +3873,12 @@ class TestSignatureObject(unittest.TestCase):
def __init__(self, b):
pass
- self.assertEqual(C(1), (2, 1))
- self.assertEqual(self.signature(C),
- ((('a', ..., ..., "positional_or_keyword"),),
- ...))
+ with self.assertWarns(FutureWarning):
+ self.assertEqual(C(1), (2, 1))
+ with self.assertWarns(FutureWarning):
+ self.assertEqual(self.signature(C),
+ ((('a', ..., ..., "positional_or_keyword"),),
+ ...))
with self.subTest('partialmethod'):
class CM(type):
@@ -4024,10 +4026,12 @@ class TestSignatureObject(unittest.TestCase):
class C:
__init__ = functools.partial(lambda x, a: None, 2)
- C(1) # does not raise
- self.assertEqual(self.signature(C),
- ((('a', ..., ..., "positional_or_keyword"),),
- ...))
+ with self.assertWarns(FutureWarning):
+ C(1) # does not raise
+ with self.assertWarns(FutureWarning):
+ self.assertEqual(self.signature(C),
+ ((('a', ..., ..., "positional_or_keyword"),),
+ ...))
with self.subTest('partialmethod'):
class C:
@@ -4282,10 +4286,13 @@ class TestSignatureObject(unittest.TestCase):
class C:
__call__ = functools.partial(lambda x, a: (x, a), 2)
- self.assertEqual(C()(1), (2, 1))
- self.assertEqual(self.signature(C()),
- ((('a', ..., ..., "positional_or_keyword"),),
- ...))
+ 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"),),
+ ...))
with self.subTest('partialmethod'):
class C:
diff --git a/Misc/NEWS.d/next/Library/2024-06-27-13-47-14.gh-issue-121027.jh55EC.rst b/Misc/NEWS.d/next/Library/2024-06-27-13-47-14.gh-issue-121027.jh55EC.rst
new file mode 100644
index 0000000..8470c8b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-06-27-13-47-14.gh-issue-121027.jh55EC.rst
@@ -0,0 +1,2 @@
+Add a future warning in :meth:`!functools.partial.__get__`. In future Python
+versions :class:`functools.partial` will be a method descriptor.
diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c
index 9dee7bf..564c271 100644
--- a/Modules/_functoolsmodule.c
+++ b/Modules/_functoolsmodule.c
@@ -197,6 +197,21 @@ partial_dealloc(partialobject *pto)
Py_DECREF(tp);
}
+static PyObject *
+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);
+}
/* Merging keyword arguments using the vectorcall convention is messy, so
* if we would need to do that, we stop using vectorcall and fall back
@@ -514,6 +529,7 @@ static PyType_Slot partial_type_slots[] = {
{Py_tp_methods, partial_methods},
{Py_tp_members, partial_memberlist},
{Py_tp_getset, partial_getsetlist},
+ {Py_tp_descr_get, (descrgetfunc)partial_descr_get},
{Py_tp_new, partial_new},
{Py_tp_free, PyObject_GC_Del},
{0, 0}