8000 gh-121027: Make the functools.partial object a method descriptor · python/cpython@5ecccee · GitHub
[go: up one dir, main page]

Skip to content

Commit 5ecccee

Browse files
serhiy-storchakad.grigonis
and
d.grigonis
committed
gh-121027: Make the functools.partial object a method descriptor
Co-authored-by: d.grigonis <dgrigonis@users.noreply.github.com>
1 parent b057ae6 commit 5ecccee

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
from collections import namedtuple
1919
# import types, weakref # Deferred to single_dispatch()
2020
from reprlib import recursive_repr
21+
from types import MethodType
2122
from _thread import RLock
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
if obj is None:
316317
return self
317-
import warnings
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-
return self
318+
return MethodType(self, obj)
323319

324320
def __reduce__(self):
325321
return type(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-
if get is not None and not isinstance(self.func, partial):
401+
if get is not None:
406402
new_func = get(obj, cls)
407403
if new_func is not self.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-
with self.assertWarns(FutureWarning) as w:
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
with self.subTest('partial'):
38703870
class CM(type):
3871-
__call__ = functools.partial(lambda x, a: (x, a), 2)
3871+
__call__ = functools.partial(lambda x, a, b: (x, a, b), 2)
38723872
class C(metaclass=CM):
3873-
def __init__(self, b):
3873+
def __init__(self, c):
38743874
pass
38753875

3876-
with self.assertWarns(FutureWarning):
3877-
self.assertEqual(C(1), (2, 1))
3878-
with self.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
with self.subTest('partialmethod'):
38843882
class CM(type):
@@ -4024,14 +4022,12 @@ class C:
40244022

40254023
with self.subTest('partial'):
40264024
class C:
4027-
__init__ = functools.partial(lambda x, a: None, 2)
4025+
__init__ = functools.partial(lambda x, a, b: None, 2)
40284026

4029-
with self.assertWarns(FutureWarning):
4030-
C(1) # does not raise
4031-
with self.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
with self.subTest('partialmethod'):
40374033
class C:
@@ -4284,15 +4280,13 @@ class C:
42844280

42854281
with self.subTest('partial'):
42864282
class C:
4287-
__call__ = functools.partial(lambda x, a: (x, a), 2)
4283+
__call__ = functools.partial(lambda x, a, b: (x, a, b), 2)
42884284

42894285
c = C()
4290-
with self.assertWarns(FutureWarning):
4291-
self.assertEqual(c(1), (2, 1))
4292-
with self.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
with self.subTest('partialmethod'):
42984292
class C:
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
return Py_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-
return NULL;
212-
}
213-
return Py_NewRef(self);
206+
return PyMethod_New(self, obj);
214207
}
215208

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

0 commit comments

Comments
 (0)
0