Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitdb96edd

Browse files
gh-121027: Add a future warning in functools.partial.__get__ (#121086)
1 parent223c03a commitdb96edd

File tree

7 files changed

+75
-17
lines changed

7 files changed

+75
-17
lines changed

‎Doc/whatsnew/3.13.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,6 +2262,12 @@ Changes in the Python API
22622262
returned by:meth:`zipfile.ZipFile.open` was changed from ``'r'`` to ``'rb'``.
22632263
(Contributed by Serhiy Storchaka in:gh:`115961`.)
22642264

2265+
*:class:`functools.partial` now emits a:exc:`FutureWarning` when it is
2266+
used as a method.
2267+
Its behavior will be changed in future Python versions.
2268+
Wrap it in:func:`staticmethod` if you want to preserve the old behavior.
2269+
(Contributed by Serhiy Storchaka in:gh:`121027`.)
2270+
22652271
.. _pep667-porting-notes-py:
22662272

22672273
* Calling:func:`locals` in an:term:`optimized scope` now produces an

‎Lib/functools.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,16 @@ def __repr__(self):
311311
args.extend(f"{k}={v!r}"for (k,v)inself.keywords.items())
312312
returnf"{module}.{qualname}({', '.join(args)})"
313313

314+
def__get__(self,obj,objtype=None):
315+
ifobjisNone:
316+
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
323+
314324
def__reduce__(self):
315325
returntype(self), (self.func,), (self.func,self.args,
316326
self.keywordsorNone,self.__dict__orNone)
@@ -392,7 +402,7 @@ def _method(cls_or_self, /, *args, **keywords):
392402
def__get__(self,obj,cls=None):
393403
get=getattr(self.func,"__get__",None)
394404
result=None
395-
ifgetisnotNone:
405+
ifgetisnotNoneandnotisinstance(self.func,partial):
396406
new_func=get(obj,cls)
397407
ifnew_funcisnotself.func:
398408
# Assume __get__ returning something new indicates the

‎Lib/inspect.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2550,6 +2550,10 @@ def _signature_from_callable(obj, *,
25502550
new_params= (first_wrapped_param,)+sig_params
25512551
returnsig.replace(parameters=new_params)
25522552

2553+
ifisinstance(obj,functools.partial):
2554+
wrapped_sig=_get_signature_of(obj.func)
2555+
return_signature_get_partial(wrapped_sig,obj)
2556+
25532557
ifisfunction(obj)or_signature_is_functionlike(obj):
25542558
# If it's a pure Python function, or an object that is duck type
25552559
# of a Python function (Cython functions, for instance), then:
@@ -2561,10 +2565,6 @@ def _signature_from_callable(obj, *,
25612565
return_signature_from_builtin(sigcls,obj,
25622566
skip_bound_arg=skip_bound_arg)
25632567

2564-
ifisinstance(obj,functools.partial):
2565-
wrapped_sig=_get_signature_of(obj.func)
2566-
return_signature_get_partial(wrapped_sig,obj)
2567-
25682568
ifisinstance(obj,type):
25692569
# obj is a class or a metaclass
25702570

‎Lib/test/test_functools.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,23 @@ def __getitem__(self, key):
395395
f=self.partial(object)
396396
self.assertRaises(TypeError,f.__setstate__,BadSequence())
397397

398+
deftest_partial_as_method(self):
399+
classA:
400+
meth=self.partial(capture,1,a=2)
401+
cmeth=classmethod(self.partial(capture,1,a=2))
402+
smeth=staticmethod(self.partial(capture,1,a=2))
403+
404+
a=A()
405+
self.assertEqual(A.meth(3,b=4), ((1,3), {'a':2,'b':4}))
406+
self.assertEqual(A.cmeth(3,b=4), ((1,A,3), {'a':2,'b':4}))
407+
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__)
411+
self.assertEqual(a.cmeth(3,b=4), ((1,A,3), {'a':2,'b':4}))
412+
self.assertEqual(a.smeth(3,b=4), ((1,3), {'a':2,'b':4}))
413+
414+
398415
@unittest.skipUnless(c_functools,'requires the C _functools module')
399416
classTestPartialC(TestPartial,unittest.TestCase):
400417
ifc_functools:

‎Lib/test/test_inspect/test_inspect.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3873,10 +3873,12 @@ class C(metaclass=CM):
38733873
def__init__(self,b):
38743874
pass
38753875

3876-
self.assertEqual(C(1), (2,1))
3877-
self.assertEqual(self.signature(C),
3878-
((('a', ..., ...,"positional_or_keyword"),),
3879-
...))
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+
...))
38803882

38813883
withself.subTest('partialmethod'):
38823884
classCM(type):
@@ -4024,10 +4026,12 @@ class C:
40244026
classC:
40254027
__init__=functools.partial(lambdax,a:None,2)
40264028

4027-
C(1)# does not raise
4028-
self.assertEqual(self.signature(C),
4029-
((('a', ..., ...,"positional_or_keyword"),),
4030-
...))
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+
...))
40314035

40324036
withself.subTest('partialmethod'):
40334037
classC:
@@ -4282,10 +4286,13 @@ class C:
42824286
classC:
42834287
__call__=functools.partial(lambdax,a: (x,a),2)
42844288

4285-
self.assertEqual(C()(1), (2,1))
4286-
self.assertEqual(self.signature(C()),
4287-
((('a', ..., ...,"positional_or_keyword"),),
4288-
...))
4289+
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+
...))
42894296

42904297
withself.subTest('partialmethod'):
42914298
classC:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add a future warning in:meth:`!functools.partial.__get__`. In future Python
2+
versions:class:`functools.partial` will be a method descriptor.

‎Modules/_functoolsmodule.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,21 @@ partial_dealloc(partialobject *pto)
197197
Py_DECREF(tp);
198198
}
199199

200+
staticPyObject*
201+
partial_descr_get(PyObject*self,PyObject*obj,PyObject*type)
202+
{
203+
if (obj==Py_None||obj==NULL) {
204+
returnPy_NewRef(self);
205+
}
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);
214+
}
200215

201216
/* Merging keyword arguments using the vectorcall convention is messy, so
202217
* if we would need to do that, we stop using vectorcall and fall back
@@ -514,6 +529,7 @@ static PyType_Slot partial_type_slots[] = {
514529
{Py_tp_methods,partial_methods},
515530
{Py_tp_members,partial_memberlist},
516531
{Py_tp_getset,partial_getsetlist},
532+
{Py_tp_descr_get, (descrgetfunc)partial_descr_get},
517533
{Py_tp_new,partial_new},
518534
{Py_tp_free,PyObject_GC_Del},
519535
{0,0}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp