Skip to content

Commit 693fbc7

Browse files
serhiy-storchakad.grigonis
andcommitted
gh-121027: Make the functools.partial object a method descriptor
Co-authored-by: d.grigonis <dgrigonis@users.noreply.github.com>
1 parent fa4be09 commit 693fbc7

File tree

6 files changed

+28
-40
lines changed

6 files changed

+28
-40
lines changed

‎Doc/whatsnew/3.14.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,12 @@ Porting to Python 3.14
292292
This section lists previously described changes and other bugfixes
293293
that may require changes to your code.
294294

295+
Changes in the Python API
296+
-------------------------
297+
298+
* :class:`functools.partial` is now a method descriptor.
299+
Wrap it in :func:`staticmethod` if you want to preserve the old behavior.
300+
(Contributed by Serhiy Storchaka and D. Grigonis in :gh:`121027`.)
295301

296302
Build Changes
297303
=============

‎Lib/functools.py‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
fromcollectionsimportnamedtuple
1919
# import types, weakref # Deferred to single_dispatch()
2020
fromreprlibimportrecursive_repr
21+
fromtypesimportMethodType
2122
from_threadimportRLock
2223

2324
# Avoid importing types, so we can speedup import time
@@ -314,12 +315,7 @@ def __repr__(self):
314315
def__get__(self, obj, objtype=None):
315316
ifobjisNone:
316317
returnself
317-
importwarnings
318-
warnings.warn('functools.partial will be a method descriptor in '
319-
'future Python versions; wrap it in staticmethod() '
320-
'if you want to preserve the old behavior',
321-
FutureWarning, 2)
322-
returnself
318+
returnMethodType(self, obj)
323319

324320
def__reduce__(self):
325321
returntype(self), (self.func,), (self.func, self.args,
@@ -402,7 +398,7 @@ def _method(cls_or_self, /, *args, **keywords):
402398
def__get__(self, obj, cls=None):
403399
get=getattr(self.func, "__get__", None)
404400
result=None
405-
ifgetisnotNoneandnotisinstance(self.func, partial):
401+
ifgetisnotNone:
406402
new_func=get(obj, cls)
407403
ifnew_funcisnotself.func:
408404
# Assume __get__ returning something new indicates the

‎Lib/test/test_functools.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,7 @@ class A:
405405
self.assertEqual(A.meth(3, b=4), ((1, 3),{'a': 2, 'b': 4}))
406406
self.assertEqual(A.cmeth(3, b=4), ((1, A, 3),{'a': 2, 'b': 4}))
407407
self.assertEqual(A.smeth(3, b=4), ((1, 3),{'a': 2, 'b': 4}))
408-
withself.assertWarns(FutureWarning) asw:
409-
self.assertEqual(a.meth(3, b=4), ((1, 3),{'a': 2, 'b': 4}))
410-
self.assertEqual(w.filename, __file__)
408+
self.assertEqual(a.meth(3, b=4), ((1, a, 3),{'a': 2, 'b': 4}))
411409
self.assertEqual(a.cmeth(3, b=4), ((1, A, 3),{'a': 2, 'b': 4}))
412410
self.assertEqual(a.smeth(3, b=4), ((1, 3),{'a': 2, 'b': 4}))
413411

‎Lib/test/test_inspect/test_inspect.py‎

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3868,17 +3868,15 @@ def __init__(self, b):
38683868

38693869
withself.subTest('partial'):
38703870
classCM(type):
3871-
__call__=functools.partial(lambdax, a: (x, a), 2)
3871+
__call__=functools.partial(lambdax, a, b: (x, a, b), 2)
38723872
classC(metaclass=CM):
3873-
def__init__(self, b):
3873+
def__init__(self, c):
38743874
pass
38753875

3876-
withself.assertWarns(FutureWarning):
3877-
self.assertEqual(C(1), (2, 1))
3878-
withself.assertWarns(FutureWarning):
3879-
self.assertEqual(self.signature(C),
3880-
((('a', ..., ..., "positional_or_keyword"),),
3881-
...))
3876+
self.assertEqual(C(1), (2, C, 1))
3877+
self.assertEqual(self.signature(C),
3878+
((('b', ..., ..., "positional_or_keyword"),),
3879+
...))
38823880

38833881
withself.subTest('partialmethod'):
38843882
classCM(type):
@@ -4024,14 +4022,12 @@ class C:
40244022

40254023
withself.subTest('partial'):
40264024
classC:
4027-
__init__=functools.partial(lambdax, a: None, 2)
4025+
__init__=functools.partial(lambdax, a, b: None, 2)
40284026

4029-
withself.assertWarns(FutureWarning):
4030-
C(1) # does not raise
4031-
withself.assertWarns(FutureWarning):
4032-
self.assertEqual(self.signature(C),
4033-
((('a', ..., ..., "positional_or_keyword"),),
4034-
...))
4027+
C(1) # does not raise
4028+
self.assertEqual(self.signature(C),
4029+
((('b', ..., ..., "positional_or_keyword"),),
4030+
...))
40354031

40364032
withself.subTest('partialmethod'):
40374033
classC:
@@ -4284,15 +4280,13 @@ class C:
42844280

42854281
withself.subTest('partial'):
42864282
classC:
4287-
__call__=functools.partial(lambdax, a: (x, a), 2)
4283+
__call__=functools.partial(lambdax, a, b: (x, a, b), 2)
42884284

42894285
c=C()
4290-
withself.assertWarns(FutureWarning):
4291-
self.assertEqual(c(1), (2, 1))
4292-
withself.assertWarns(FutureWarning):
4293-
self.assertEqual(self.signature(c),
4294-
((('a', ..., ..., "positional_or_keyword"),),
4295-
...))
4286+
self.assertEqual(c(1), (2, c, 1))
4287+
self.assertEqual(self.signature(C()),
4288+
((('b', ..., ..., "positional_or_keyword"),),
4289+
...))
42964290

42974291
withself.subTest('partialmethod'):
42984292
classC:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make the :class:`functools.partial` object a method descriptor.

‎Modules/_functoolsmodule.c‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -203,14 +203,7 @@ partial_descr_get(PyObject *self, PyObject *obj, PyObject *type)
203203
if (obj==Py_None||obj==NULL){
204204
returnPy_NewRef(self);
205205
}
206-
if (PyErr_WarnEx(PyExc_FutureWarning,
207-
"functools.partial will be a method descriptor in "
208-
"future Python versions; wrap it in staticmethod() "
209-
"if you want to preserve the old behavior", 1) <0)
210-
{
211-
returnNULL;
212-
}
213-
returnPy_NewRef(self);
206+
returnPyMethod_New(self, obj);
214207
}
215208

216209
/* Merging keyword arguments using the vectorcall convention is messy, so

0 commit comments

Comments
(0)