gh-121027: Add a future warning in functools.partial.__get__ by serhiy-storchaka · Pull Request #121086 · python/cpython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.13.rst
12 changes: 11 additions & 1 deletion Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,16 @@ def __repr__(self):
args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
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):
return type(self), (self.func,), (self.func, self.args,
self.keywords or None, self.__dict__ or None)
Expand Down Expand Up @@ -392,7 +402,7 @@ def _method(cls_or_self, /, *args, **keywords):
def __get__(self, obj, cls=None):
get = getattr(self.func, "__get__", None)
result = None
if get is not None:
if get is not None and not isinstance(self.func, partial):
new_func = get(obj, cls)
if new_func is not self.func:
# Assume __get__ returning something new indicates the
Expand Down
8 changes: 4 additions & 4 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2550,6 +2550,10 @@ def _signature_from_callable(obj, *,
new_params = (first_wrapped_param,) + sig_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 it's a pure Python function, or an object that is duck type
# of a Python function (Cython functions, for instance), then:
Expand All @@ -2561,10 +2565,6 @@ def _signature_from_callable(obj, *,
return _signature_from_builtin(sigcls, obj,
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):
# obj is a class or a metaclass

Expand Down
17 changes: 17 additions & 0 deletions Lib/test/test_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,23 @@ def __getitem__(self, key):
f = self.partial(object)
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')
class TestPartialC(TestPartial, unittest.TestCase):
if c_functools:
Expand Down
31 changes: 19 additions & 12 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -3873,10 +3873,12 @@ class C(metaclass=CM):
def __init__(self, b):
pass

self.assertEqual(C(1), (2, 1))
self.assertEqual(self.signature(C),
((('a', ..., ..., "positional_or_keyword"),),
...))
with self.assertWarns(FutureWarning):
self.assertEqual(C(1), (2, 1))
with self.assertWarns(FutureWarning):
self.assertEqual(self.signature(C),
((('a', ..., ..., "positional_or_keyword"),),
...))

with self.subTest('partialmethod'):
class CM(type):
Expand Down Expand Up @@ -4024,10 +4026,12 @@ class C:
class C:
__init__ = functools.partial(lambda x, a: None, 2)

C(1) # does not raise
self.assertEqual(self.signature(C),
((('a', ..., ..., "positional_or_keyword"),),
...))
with self.assertWarns(FutureWarning):
C(1) # does not raise
with self.assertWarns(FutureWarning):
self.assertEqual(self.signature(C),
((('a', ..., ..., "positional_or_keyword"),),
...))

with self.subTest('partialmethod'):
class C:
Expand Down Expand Up @@ -4282,10 +4286,13 @@ class C:
class C:
__call__ = functools.partial(lambda x, a: (x, a), 2)

self.assertEqual(C()(1), (2, 1))
self.assertEqual(self.signature(C()),
((('a', ..., ..., "positional_or_keyword"),),
...))
c = C()
with self.assertWarns(FutureWarning):
self.assertEqual(c(1), (2, 1))
with self.assertWarns(FutureWarning):
self.assertEqual(self.signature(c),
((('a', ..., ..., "positional_or_keyword"),),
...))

with self.subTest('partialmethod'):
class C:
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 16 additions & 0 deletions Modules/_functoolsmodule.c