mirror of https://github.com/python/cpython
[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 db96edd6d1
)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
c7d2b2b646
commit
49e5740135
|
@ -2250,6 +2250,12 @@ Changes in the Python API
|
||||||
returned by :meth:`zipfile.ZipFile.open` was changed from ``'r'`` to ``'rb'``.
|
returned by :meth:`zipfile.ZipFile.open` was changed from ``'r'`` to ``'rb'``.
|
||||||
(Contributed by Serhiy Storchaka in :gh:`115961`.)
|
(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:
|
.. _pep667-porting-notes-py:
|
||||||
|
|
||||||
* Calling :func:`locals` in an :term:`optimized scope` now produces an
|
* Calling :func:`locals` in an :term:`optimized scope` now produces an
|
||||||
|
|
|
@ -311,6 +311,16 @@ class partial:
|
||||||
args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
|
args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
|
||||||
return f"{module}.{qualname}({', '.join(args)})"
|
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):
|
def __reduce__(self):
|
||||||
return type(self), (self.func,), (self.func, self.args,
|
return type(self), (self.func,), (self.func, self.args,
|
||||||
self.keywords or None, self.__dict__ or None)
|
self.keywords or None, self.__dict__ or None)
|
||||||
|
@ -392,7 +402,7 @@ class partialmethod(object):
|
||||||
def __get__(self, obj, cls=None):
|
def __get__(self, obj, cls=None):
|
||||||
get = getattr(self.func, "__get__", None)
|
get = getattr(self.func, "__get__", None)
|
||||||
result = None
|
result = None
|
||||||
if get is not None:
|
if get is not None and not isinstance(self.func, partial):
|
||||||
new_func = get(obj, cls)
|
new_func = get(obj, cls)
|
||||||
if new_func is not self.func:
|
if new_func is not self.func:
|
||||||
# Assume __get__ returning something new indicates the
|
# Assume __get__ returning something new indicates the
|
||||||
|
|
|
@ -2556,6 +2556,10 @@ def _signature_from_callable(obj, *,
|
||||||
new_params = (first_wrapped_param,) + sig_params
|
new_params = (first_wrapped_param,) + sig_params
|
||||||
return sig.replace(parameters=new_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 isfunction(obj) or _signature_is_functionlike(obj):
|
||||||
# If it's a pure Python function, or an object that is duck type
|
# If it's a pure Python function, or an object that is duck type
|
||||||
# of a Python function (Cython functions, for instance), then:
|
# of a Python function (Cython functions, for instance), then:
|
||||||
|
@ -2567,10 +2571,6 @@ def _signature_from_callable(obj, *,
|
||||||
return _signature_from_builtin(sigcls, obj,
|
return _signature_from_builtin(sigcls, obj,
|
||||||
skip_bound_arg=skip_bound_arg)
|
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):
|
if isinstance(obj, type):
|
||||||
# obj is a class or a metaclass
|
# obj is a class or a metaclass
|
||||||
|
|
||||||
|
|
|
@ -395,6 +395,23 @@ class TestPartial:
|
||||||
f = self.partial(object)
|
f = self.partial(object)
|
||||||
self.assertRaises(TypeError, f.__setstate__, BadSequence())
|
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')
|
@unittest.skipUnless(c_functools, 'requires the C _functools module')
|
||||||
class TestPartialC(TestPartial, unittest.TestCase):
|
class TestPartialC(TestPartial, unittest.TestCase):
|
||||||
if c_functools:
|
if c_functools:
|
||||||
|
|
|
@ -3873,7 +3873,9 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
def __init__(self, b):
|
def __init__(self, b):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
with self.assertWarns(FutureWarning):
|
||||||
self.assertEqual(C(1), (2, 1))
|
self.assertEqual(C(1), (2, 1))
|
||||||
|
with self.assertWarns(FutureWarning):
|
||||||
self.assertEqual(self.signature(C),
|
self.assertEqual(self.signature(C),
|
||||||
((('a', ..., ..., "positional_or_keyword"),),
|
((('a', ..., ..., "positional_or_keyword"),),
|
||||||
...))
|
...))
|
||||||
|
@ -4024,7 +4026,9 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
class C:
|
class C:
|
||||||
__init__ = functools.partial(lambda x, a: None, 2)
|
__init__ = functools.partial(lambda x, a: None, 2)
|
||||||
|
|
||||||
|
with self.assertWarns(FutureWarning):
|
||||||
C(1) # does not raise
|
C(1) # does not raise
|
||||||
|
with self.assertWarns(FutureWarning):
|
||||||
self.assertEqual(self.signature(C),
|
self.assertEqual(self.signature(C),
|
||||||
((('a', ..., ..., "positional_or_keyword"),),
|
((('a', ..., ..., "positional_or_keyword"),),
|
||||||
...))
|
...))
|
||||||
|
@ -4282,8 +4286,11 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
class C:
|
class C:
|
||||||
__call__ = functools.partial(lambda x, a: (x, a), 2)
|
__call__ = functools.partial(lambda x, a: (x, a), 2)
|
||||||
|
|
||||||
self.assertEqual(C()(1), (2, 1))
|
c = C()
|
||||||
self.assertEqual(self.signature(C()),
|
with self.assertWarns(FutureWarning):
|
||||||
|
self.assertEqual(c(1), (2, 1))
|
||||||
|
with self.assertWarns(FutureWarning):
|
||||||
|
self.assertEqual(self.signature(c),
|
||||||
((('a', ..., ..., "positional_or_keyword"),),
|
((('a', ..., ..., "positional_or_keyword"),),
|
||||||
...))
|
...))
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
@ -197,6 +197,21 @@ partial_dealloc(partialobject *pto)
|
||||||
Py_DECREF(tp);
|
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
|
/* Merging keyword arguments using the vectorcall convention is messy, so
|
||||||
* if we would need to do that, we stop using vectorcall and fall back
|
* 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_methods, partial_methods},
|
||||||
{Py_tp_members, partial_memberlist},
|
{Py_tp_members, partial_memberlist},
|
||||||
{Py_tp_getset, partial_getsetlist},
|
{Py_tp_getset, partial_getsetlist},
|
||||||
|
{Py_tp_descr_get, (descrgetfunc)partial_descr_get},
|
||||||
{Py_tp_new, partial_new},
|
{Py_tp_new, partial_new},
|
||||||
{Py_tp_free, PyObject_GC_Del},
|
{Py_tp_free, PyObject_GC_Del},
|
||||||
{0, 0}
|
{0, 0}
|
||||||
|
|
Loading…
Reference in New Issue