mirror of https://github.com/python/cpython
gh-121027: Make the functools.partial object a method descriptor (GH-121089)
Co-authored-by: d.grigonis <dgrigonis@users.noreply.github.com>
This commit is contained in:
parent
f09d184821
commit
ff5806c78e
|
@ -305,6 +305,12 @@ Porting to Python 3.14
|
||||||
This section lists previously described changes and other bugfixes
|
This section lists previously described changes and other bugfixes
|
||||||
that may require changes to your code.
|
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
|
Build Changes
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -18,6 +18,7 @@ from abc import get_cache_token
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
# import types, weakref # Deferred to single_dispatch()
|
# import types, weakref # Deferred to single_dispatch()
|
||||||
from reprlib import recursive_repr
|
from reprlib import recursive_repr
|
||||||
|
from types import MethodType
|
||||||
from _thread import RLock
|
from _thread import RLock
|
||||||
|
|
||||||
# Avoid importing types, so we can speedup import time
|
# Avoid importing types, so we can speedup import time
|
||||||
|
@ -314,12 +315,7 @@ class partial:
|
||||||
def __get__(self, obj, objtype=None):
|
def __get__(self, obj, objtype=None):
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return self
|
return self
|
||||||
import warnings
|
return MethodType(self, obj)
|
||||||
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,
|
||||||
|
@ -402,7 +398,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 and not isinstance(self.func, partial):
|
if get is not None:
|
||||||
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
|
||||||
|
|
|
@ -405,9 +405,7 @@ class TestPartial:
|
||||||
self.assertEqual(A.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
|
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.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4}))
|
||||||
self.assertEqual(A.smeth(3, b=4), ((1, 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, a, 3), {'a': 2, 'b': 4}))
|
||||||
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.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4}))
|
||||||
self.assertEqual(a.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
|
self.assertEqual(a.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
|
||||||
|
|
||||||
|
|
|
@ -3868,17 +3868,15 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
|
|
||||||
with self.subTest('partial'):
|
with self.subTest('partial'):
|
||||||
class CM(type):
|
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):
|
class C(metaclass=CM):
|
||||||
def __init__(self, b):
|
def __init__(self, c):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
with self.assertWarns(FutureWarning):
|
self.assertEqual(C(1), (2, C, 1))
|
||||||
self.assertEqual(C(1), (2, 1))
|
self.assertEqual(self.signature(C),
|
||||||
with self.assertWarns(FutureWarning):
|
((('b', ..., ..., "positional_or_keyword"),),
|
||||||
self.assertEqual(self.signature(C),
|
...))
|
||||||
((('a', ..., ..., "positional_or_keyword"),),
|
|
||||||
...))
|
|
||||||
|
|
||||||
with self.subTest('partialmethod'):
|
with self.subTest('partialmethod'):
|
||||||
class CM(type):
|
class CM(type):
|
||||||
|
@ -4024,14 +4022,12 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
|
|
||||||
with self.subTest('partial'):
|
with self.subTest('partial'):
|
||||||
class C:
|
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
|
||||||
C(1) # does not raise
|
self.assertEqual(self.signature(C),
|
||||||
with self.assertWarns(FutureWarning):
|
((('b', ..., ..., "positional_or_keyword"),),
|
||||||
self.assertEqual(self.signature(C),
|
...))
|
||||||
((('a', ..., ..., "positional_or_keyword"),),
|
|
||||||
...))
|
|
||||||
|
|
||||||
with self.subTest('partialmethod'):
|
with self.subTest('partialmethod'):
|
||||||
class C:
|
class C:
|
||||||
|
@ -4284,15 +4280,13 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
|
|
||||||
with self.subTest('partial'):
|
with self.subTest('partial'):
|
||||||
class C:
|
class C:
|
||||||
__call__ = functools.partial(lambda x, a: (x, a), 2)
|
__call__ = functools.partial(lambda x, a, b: (x, a, b), 2)
|
||||||
|
|
||||||
c = C()
|
c = C()
|
||||||
with self.assertWarns(FutureWarning):
|
self.assertEqual(c(1), (2, c, 1))
|
||||||
self.assertEqual(c(1), (2, 1))
|
self.assertEqual(self.signature(C()),
|
||||||
with self.assertWarns(FutureWarning):
|
((('b', ..., ..., "positional_or_keyword"),),
|
||||||
self.assertEqual(self.signature(c),
|
...))
|
||||||
((('a', ..., ..., "positional_or_keyword"),),
|
|
||||||
...))
|
|
||||||
|
|
||||||
with self.subTest('partialmethod'):
|
with self.subTest('partialmethod'):
|
||||||
class C:
|
class C:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Make the :class:`functools.partial` object a method descriptor.
|
|
@ -203,14 +203,7 @@ partial_descr_get(PyObject *self, PyObject *obj, PyObject *type)
|
||||||
if (obj == Py_None || obj == NULL) {
|
if (obj == Py_None || obj == NULL) {
|
||||||
return Py_NewRef(self);
|
return Py_NewRef(self);
|
||||||
}
|
}
|
||||||
if (PyErr_WarnEx(PyExc_FutureWarning,
|
return PyMethod_New(self, obj);
|
||||||
"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
|
||||||
|
|
Loading…
Reference in New Issue