diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index b463c0b6d0e402..f11daf12e76585 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -1046,6 +1046,21 @@ Classes and functions order of keyword-only parameters as of version 3.7, although in practice this order had always been preserved in Python 3. + .. deprecated-removed:: 3.13, 3.15 + Now all :func:`getfullargspec` differences from :class:`Signature` + can be solved by passing ``follow_wrapped=False, skip_bound_arg=False`` + arguments:: + + from inspect import signature + + signature(your_obj, follow_wrapped=False, skip_bound_arg=False) + + For Python versions older than 3.13 you can use ``inspect313`` PyPI package:: + + from inspect313 import signature + + signature(your_obj, follow_wrapped=False, skip_bound_arg=False) + .. function:: getargvalues(frame) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index fc5ae13abe7961..b04ae00f9f0476 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -587,6 +587,11 @@ Pending Removal in Python 3.15 All arguments will be removed from :func:`threading.RLock` in Python 3.15. (Contributed by Nikita Sobolev in :gh:`102029`.) +* :func:`inspect.getfullargspec` is deprecated and slated for removal in 3.15, + use :func:`inspect.signature` instead or ``inspect313`` PyPI package + for older versions. + (Contributed by Nikita Sobolev in :gh:`108901`.) + Pending Removal in Python 3.16 ------------------------------ diff --git a/Lib/inspect.py b/Lib/inspect.py index aaa22bef896602..4ced275820785f 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1403,6 +1403,13 @@ def getfullargspec(func): - the "self" parameter is always reported, even for bound methods - wrapper chains defined by __wrapped__ *not* unwrapped automatically """ + import warnings + warnings._deprecated( + "getfullargspec", + '{name!r} is deprecated since 3.13 and slated for removal in Python {remove}, ' + 'use `inspect.Signature` or `inspect313` for older Python versions instead', + remove=(3, 15), + ) try: # Re: `skip_bound_arg=False` # @@ -1579,7 +1586,10 @@ def getcallargs(func, /, *positional, **named): A dict is returned, with keys the function argument names (including the names of the * and ** arguments, if any), and values the respective bound values from 'positional' and 'named'.""" - spec = getfullargspec(func) + import warnings + with warnings.catch_warnings(): + warnings.simplefilter('ignore', category=DeprecationWarning) + spec = getfullargspec(func) args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec f_name = func.__name__ arg2value = {} @@ -3103,10 +3113,12 @@ def __init__(self, parameters=None, *, return_annotation=_empty, @classmethod def from_callable(cls, obj, *, - follow_wrapped=True, globals=None, locals=None, eval_str=False): + follow_wrapped=True, skip_bound_arg=True, + globals=None, locals=None, eval_str=False): """Constructs Signature for the given callable object.""" return _signature_from_callable(obj, sigcls=cls, follow_wrapper_chains=follow_wrapped, + skip_bound_arg=skip_bound_arg, globals=globals, locals=locals, eval_str=eval_str) @property @@ -3361,9 +3373,11 @@ def __str__(self): return rendered -def signature(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False): +def signature(obj, *, follow_wrapped=True, skip_bound_arg=True, + globals=None, locals=None, eval_str=False): """Get a signature object for the passed callable.""" return Signature.from_callable(obj, follow_wrapped=follow_wrapped, + skip_bound_arg=skip_bound_arg, globals=globals, locals=locals, eval_str=eval_str) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index becbb0498bbb3f..cf415c9ccc55d8 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1078,6 +1078,16 @@ def attrs_wo_objs(cls): class TestClassesAndFunctions(unittest.TestCase): + def assertDeprecated(self): + import re + return self.assertWarnsRegex( + DeprecationWarning, + re.escape( + "'getfullargspec' is deprecated since 3.13 " + "and slated for removal in Python 3.15" + ), + ) + def test_newstyle_mro(self): # The same w/ new-class MRO. class A(object): pass @@ -1094,8 +1104,9 @@ def assertFullArgSpecEquals(self, routine, args_e, varargs_e=None, posonlyargs_e=[], kwonlyargs_e=[], kwonlydefaults_e=None, ann_e={}): - args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \ - inspect.getfullargspec(routine) + with self.assertDeprecated(): + args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \ + inspect.getfullargspec(routine) self.assertEqual(args, args_e) self.assertEqual(varargs, varargs_e) self.assertEqual(varkw, varkw_e) @@ -1148,11 +1159,13 @@ def test(): def test_getfullargspec_signature_annos(self): def test(a:'spam') -> 'ham': pass - spec = inspect.getfullargspec(test) + with self.assertDeprecated(): + spec = inspect.getfullargspec(test) self.assertEqual(test.__annotations__, spec.annotations) def test(): pass - spec = inspect.getfullargspec(test) + with self.assertDeprecated(): + spec = inspect.getfullargspec(test) self.assertEqual(test.__annotations__, spec.annotations) @unittest.skipIf(MISSING_C_DOCSTRINGS, @@ -1174,7 +1187,8 @@ def test_getfullargspec_builtin_methods(self): def test_getfullargspec_builtin_func(self): import _testcapi builtin = _testcapi.docstring_with_signature_with_defaults - spec = inspect.getfullargspec(builtin) + with self.assertDeprecated(): + spec = inspect.getfullargspec(builtin) self.assertEqual(spec.defaults[0], 'avocado') @cpython_only @@ -1183,7 +1197,7 @@ def test_getfullargspec_builtin_func(self): def test_getfullargspec_builtin_func_no_signature(self): import _testcapi builtin = _testcapi.docstring_no_signature - with self.assertRaises(TypeError): + with self.assertRaises(TypeError), self.assertDeprecated(): inspect.getfullargspec(builtin) cls = _testcapi.DocStringNoSignatureTest @@ -1224,17 +1238,22 @@ def test_getfullargspec_builtin_func_no_signature(self): tests.append((stat.S_IMODE, meth_o)) for builtin, template in tests: with self.subTest(builtin): - self.assertEqual(inspect.getfullargspec(builtin), - inspect.getfullargspec(template)) + with self.assertDeprecated(): + builtin_args = inspect.getfullargspec(builtin) + with self.assertDeprecated(): + template_args = inspect.getfullargspec(template) + self.assertEqual(builtin_args, template_args) def test_getfullargspec_definition_order_preserved_on_kwonly(self): for fn in signatures_with_lexicographic_keyword_only_parameters(): - signature = inspect.getfullargspec(fn) + with self.assertDeprecated(): + signature = inspect.getfullargspec(fn) l = list(signature.kwonlyargs) sorted_l = sorted(l) self.assertTrue(l) self.assertEqual(l, sorted_l) - signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn) + with self.assertDeprecated(): + signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn) l = list(signature.kwonlyargs) self.assertEqual(l, unsorted_keyword_only_parameters) @@ -4136,6 +4155,49 @@ class D2(D1): self.assertEqual(inspect.signature(D2), inspect.signature(D1)) + def test_signature_as_getfullargspec_replacement(self): + def decorator(func): + @functools.wraps(func) # set `__wrapper__` attribute + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + return wrapper + + @decorator + def func(a: int, /, b: str = '', *, c : bool = True) -> int: ... + + sig = inspect.signature(func, follow_wrapped=False, skip_bound_arg=False) + self.assertEqual(str(sig), '(*args, **kwargs) -> int') + self.assertEqual( + str(sig), + str(inspect.Signature.from_callable(func, + follow_wrapped=False, + skip_bound_arg=False)), + ) + + class My: + def method(self, arg: int) -> None: ... + @classmethod + def cl(cls, arg2: str) -> None: ... + + sigs = { + My.method: '(self, arg: int) -> None', + My().method: '(self, arg: int) -> None', + My.cl: '(cls, arg2: str) -> None', + My().cl: '(cls, arg2: str) -> None', + } + for f, text_sig in sigs.items(): + with self.subTest(f=f): + sig = inspect.signature(f, + follow_wrapped=False, + skip_bound_arg=False) + self.assertEqual(str(sig), text_sig) + self.assertEqual( + str(sig), + str(inspect.Signature.from_callable(f, + follow_wrapped=False, + skip_bound_arg=False)), + ) + class TestParameterObject(unittest.TestCase): def test_signature_parameter_kinds(self): diff --git a/Misc/NEWS.d/next/Library/2023-11-22-15-37-18.gh-issue-108901.dHdRIo.rst b/Misc/NEWS.d/next/Library/2023-11-22-15-37-18.gh-issue-108901.dHdRIo.rst new file mode 100644 index 00000000000000..80b34d0eaedfa8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-22-15-37-18.gh-issue-108901.dHdRIo.rst @@ -0,0 +1,4 @@ +Deprecate :func:`inspect.getfullargspec` and slate it for removal in 3.15. +Instead use :func:`inspect.signature` with ``follow_wrapped=False, +skip_bound_arg=False`` arguments or ``inspect313`` PyPI package for Python +versions older than 3.13