diff options
author | Pablo Galindo <Pablogsal@gmail.com> | 2019-04-30 01:01:14 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-04-30 01:01:14 (GMT) |
commit | d5d2b4546939b98244708e5bb0cfccd55b99d244 (patch) | |
tree | 2a98e93bcad785c97f88154d275a4662afa40f8c | |
parent | 81c5a905951aaf46f292eb459c32649c0b74ef61 (diff) | |
download | cpython-d5d2b4546939b98244708e5bb0cfccd55b99d244.zip cpython-d5d2b4546939b98244708e5bb0cfccd55b99d244.tar.gz cpython-d5d2b4546939b98244708e5bb0cfccd55b99d244.tar.bz2 |
bpo-36751: Deprecate getfullargspec and report positional-only args as regular args (GH-13016)
* bpo-36751: Deprecate getfullargspec and report positional-only args as regular args
* Use inspect.signature in testhelpers
-rw-r--r-- | Doc/library/inspect.rst | 5 | ||||
-rw-r--r-- | Doc/whatsnew/3.8.rst | 4 | ||||
-rw-r--r-- | Lib/inspect.py | 43 | ||||
-rw-r--r-- | Lib/test/test_inspect.py | 76 | ||||
-rw-r--r-- | Lib/unittest/test/testmock/testhelpers.py | 4 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2019-04-29-23-30-21.bpo-36751.3NCRbm.rst | 3 |
6 files changed, 74 insertions, 61 deletions
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 81824dd..d12f122 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -948,6 +948,11 @@ Classes and functions APIs. This function is retained primarily for use in code that needs to maintain compatibility with the Python 2 ``inspect`` module API. + .. deprecated:: 3.8 + Use :func:`signature` and + :ref:`Signature Object <inspect-signature-object>`, which provide a + better introspecting API for callables. + .. versionchanged:: 3.4 This function is now based on :func:`signature`, but still ignores ``__wrapped__`` attributes and includes the already bound first diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index ac57335..bbc55dd 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -726,6 +726,10 @@ Deprecated <positional-only_parameter>`. (Contributed by Serhiy Storchaka in :issue:`36492`.) +* The function :func:`~inspect.getfullargspec` in the :mod:`inspect` + module is deprecated in favor of the :func:`inspect.signature` + API. (Contributed by Pablo Galindo in :issue:`36751`.) + API and Feature Removals ======================== diff --git a/Lib/inspect.py b/Lib/inspect.py index 3201e79..fffca22 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1081,16 +1081,15 @@ def getargspec(func): warnings.warn("inspect.getargspec() is deprecated since Python 3.0, " "use inspect.signature() or inspect.getfullargspec()", DeprecationWarning, stacklevel=2) - args, varargs, varkw, defaults, posonlyargs, kwonlyargs, \ - kwonlydefaults, ann = getfullargspec(func) - if posonlyargs or kwonlyargs or ann: - raise ValueError("Function has positional-only, keyword-only parameters" - " or annotations, use getfullargspec() API which can" - " support them") + args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \ + getfullargspec(func) + if kwonlyargs or ann: + raise ValueError("Function has keyword-only parameters or annotations" + ", use inspect.signature() API which can support them") return ArgSpec(args, varargs, varkw, defaults) FullArgSpec = namedtuple('FullArgSpec', - 'args, varargs, varkw, defaults, posonlyargs, kwonlyargs, kwonlydefaults, annotations') + 'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations') def getfullargspec(func): """Get the names and default values of a callable object's parameters. @@ -1104,11 +1103,16 @@ def getfullargspec(func): 'kwonlydefaults' is a dictionary mapping names from kwonlyargs to defaults. 'annotations' is a dictionary mapping parameter names to annotations. + .. deprecated:: 3.8 + Use inspect.signature() instead of inspect.getfullargspec(). + Notable differences from inspect.signature(): - the "self" parameter is always reported, even for bound methods - wrapper chains defined by __wrapped__ *not* unwrapped automatically """ + warnings.warn("Use inspect.signature() instead of inspect.getfullargspec()", + DeprecationWarning) try: # Re: `skip_bound_arg=False` # @@ -1182,8 +1186,8 @@ def getfullargspec(func): # compatibility with 'func.__defaults__' defaults = None - return FullArgSpec(args, varargs, varkw, defaults, - posonlyargs, kwonlyargs, kwdefaults, annotations) + return FullArgSpec(posonlyargs + args, varargs, varkw, defaults, + kwonlyargs, kwdefaults, annotations) ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals') @@ -1214,8 +1218,7 @@ def formatannotationrelativeto(object): return _formatannotation def formatargspec(args, varargs=None, varkw=None, defaults=None, - posonlyargs=(), kwonlyargs=(), kwonlydefaults={}, - annotations={}, + kwonlyargs=(), kwonlydefaults={}, annotations={}, formatarg=str, formatvarargs=lambda name: '*' + name, formatvarkw=lambda name: '**' + name, @@ -1248,17 +1251,12 @@ def formatargspec(args, varargs=None, varkw=None, defaults=None, return result specs = [] if defaults: - firstdefault = len(posonlyargs) + len(args) - len(defaults) - posonly_left = len(posonlyargs) - for i, arg in enumerate([*posonlyargs, *args]): + firstdefault = len(args) - len(defaults) + for i, arg in enumerate(args): spec = formatargandannotation(arg) if defaults and i >= firstdefault: spec = spec + formatvalue(defaults[i - firstdefault]) specs.append(spec) - posonly_left -= 1 - if posonlyargs and posonly_left == 0: - specs.append('/') - if varargs is not None: specs.append(formatvarargs(formatargandannotation(varargs))) else: @@ -1346,8 +1344,7 @@ def getcallargs(*func_and_positional, **named): func = func_and_positional[0] positional = func_and_positional[1:] spec = getfullargspec(func) - (args, varargs, varkw, defaults, posonlyargs, - kwonlyargs, kwonlydefaults, ann) = spec + args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec f_name = func.__name__ arg2value = {} @@ -1356,16 +1353,12 @@ def getcallargs(*func_and_positional, **named): # implicit 'self' (or 'cls' for classmethods) argument positional = (func.__self__,) + positional num_pos = len(positional) - num_posonlyargs = len(posonlyargs) num_args = len(args) num_defaults = len(defaults) if defaults else 0 - n = min(num_pos, num_posonlyargs) - for i in range(num_posonlyargs): - arg2value[posonlyargs[i]] = positional[i] n = min(num_pos, num_args) for i in range(n): - arg2value[args[i]] = positional[num_posonlyargs+i] + arg2value[args[i]] = positional[i] if varargs: arg2value[varargs] = tuple(positional[n:]) possible_kwargs = set(args + kwonlyargs) diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 06f8d69..3c825b0 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -766,28 +766,29 @@ class TestClassesAndFunctions(unittest.TestCase): posonlyargs_e=[], kwonlyargs_e=[], kwonlydefaults_e=None, ann_e={}, formatted=None): - args, varargs, varkw, defaults, posonlyargs, kwonlyargs, kwonlydefaults, ann = \ - inspect.getfullargspec(routine) + with self.assertWarns(DeprecationWarning): + 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) self.assertEqual(defaults, defaults_e) - self.assertEqual(posonlyargs, posonlyargs_e) self.assertEqual(kwonlyargs, kwonlyargs_e) self.assertEqual(kwonlydefaults, kwonlydefaults_e) self.assertEqual(ann, ann_e) if formatted is not None: with self.assertWarns(DeprecationWarning): self.assertEqual(inspect.formatargspec(args, varargs, varkw, defaults, - posonlyargs, kwonlyargs, - kwonlydefaults, ann), + kwonlyargs, kwonlydefaults, ann), formatted) def test_getargspec(self): self.assertArgSpecEquals(mod.eggs, ['x', 'y'], formatted='(x, y)') - self.assertRaises(ValueError, self.assertArgSpecEquals, - mod.spam, []) + self.assertArgSpecEquals(mod.spam, + ['a', 'b', 'c', 'd', 'e', 'f'], + 'g', 'h', (3, 4, 5), + '(a, b, c, d=3, e=4, f=5, *g, **h)') self.assertRaises(ValueError, self.assertArgSpecEquals, mod2.keyworded, []) @@ -811,25 +812,22 @@ class TestClassesAndFunctions(unittest.TestCase): kwonlyargs_e=['arg'], formatted='(*, arg)') - self.assertFullArgSpecEquals(mod2.all_markers, ['c', 'd'], - posonlyargs_e=['a', 'b'], + self.assertFullArgSpecEquals(mod2.all_markers, ['a', 'b', 'c', 'd'], kwonlyargs_e=['e', 'f'], - formatted='(a, b, /, c, d, *, e, f)') + formatted='(a, b, c, d, *, e, f)') self.assertFullArgSpecEquals(mod2.all_markers_with_args_and_kwargs, - ['c', 'd'], - posonlyargs_e=['a', 'b'], + ['a', 'b', 'c', 'd'], varargs_e='args', varkw_e='kwargs', kwonlyargs_e=['e', 'f'], - formatted='(a, b, /, c, d, *args, e, f, **kwargs)') + formatted='(a, b, c, d, *args, e, f, **kwargs)') - self.assertFullArgSpecEquals(mod2.all_markers_with_defaults, ['c', 'd'], + self.assertFullArgSpecEquals(mod2.all_markers_with_defaults, ['a', 'b', 'c', 'd'], defaults_e=(1,2,3), - posonlyargs_e=['a', 'b'], kwonlyargs_e=['e', 'f'], kwonlydefaults_e={'e': 4, 'f': 5}, - formatted='(a, b=1, /, c=2, d=3, *, e=4, f=5)') + formatted='(a, b=1, c=2, d=3, *, e=4, f=5)') def test_argspec_api_ignores_wrapped(self): # Issue 20684: low level introspection API must ignore __wrapped__ @@ -877,25 +875,27 @@ class TestClassesAndFunctions(unittest.TestCase): spam_param = inspect.Parameter('spam', inspect.Parameter.POSITIONAL_ONLY) test.__signature__ = inspect.Signature(parameters=(spam_param,)) - self.assertFullArgSpecEquals(test, [], posonlyargs_e=['spam'], formatted='(spam, /)') + self.assertFullArgSpecEquals(test, ['spam'], formatted='(spam)') def test_getfullargspec_signature_annos(self): def test(a:'spam') -> 'ham': pass - spec = inspect.getfullargspec(test) + with self.assertWarns(DeprecationWarning): + spec = inspect.getfullargspec(test) self.assertEqual(test.__annotations__, spec.annotations) def test(): pass - spec = inspect.getfullargspec(test) + with self.assertWarns(DeprecationWarning): + spec = inspect.getfullargspec(test) self.assertEqual(test.__annotations__, spec.annotations) @unittest.skipIf(MISSING_C_DOCSTRINGS, "Signature information for builtins requires docstrings") def test_getfullargspec_builtin_methods(self): - self.assertFullArgSpecEquals(_pickle.Pickler.dump, [], - posonlyargs_e=['self', 'obj'], formatted='(self, obj, /)') + self.assertFullArgSpecEquals(_pickle.Pickler.dump, ['self', 'obj'], + formatted='(self, obj)') - self.assertFullArgSpecEquals(_pickle.Pickler(io.BytesIO()).dump, [], - posonlyargs_e=['self', 'obj'], formatted='(self, obj, /)') + self.assertFullArgSpecEquals(_pickle.Pickler(io.BytesIO()).dump, ['self', 'obj'], + formatted='(self, obj)') self.assertFullArgSpecEquals( os.stat, @@ -910,7 +910,8 @@ class TestClassesAndFunctions(unittest.TestCase): def test_getfullargspec_builtin_func(self): import _testcapi builtin = _testcapi.docstring_with_signature_with_defaults - spec = inspect.getfullargspec(builtin) + with self.assertWarns(DeprecationWarning): + spec = inspect.getfullargspec(builtin) self.assertEqual(spec.defaults[0], 'avocado') @cpython_only @@ -919,17 +920,20 @@ class TestClassesAndFunctions(unittest.TestCase): def test_getfullargspec_builtin_func_no_signature(self): import _testcapi builtin = _testcapi.docstring_no_signature - with self.assertRaises(TypeError): - inspect.getfullargspec(builtin) + with self.assertWarns(DeprecationWarning): + with self.assertRaises(TypeError): + inspect.getfullargspec(builtin) def test_getfullargspec_definition_order_preserved_on_kwonly(self): for fn in signatures_with_lexicographic_keyword_only_parameters(): - signature = inspect.getfullargspec(fn) + with self.assertWarns(DeprecationWarning): + 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.assertWarns(DeprecationWarning): + signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn) l = list(signature.kwonlyargs) self.assertEqual(l, unsorted_keyword_only_parameters) @@ -1386,8 +1390,9 @@ class TestGetcallargsFunctions(unittest.TestCase): def assertEqualCallArgs(self, func, call_params_string, locs=None): locs = dict(locs or {}, func=func) r1 = eval('func(%s)' % call_params_string, None, locs) - r2 = eval('inspect.getcallargs(func, %s)' % call_params_string, None, - locs) + with self.assertWarns(DeprecationWarning): + r2 = eval('inspect.getcallargs(func, %s)' % call_params_string, None, + locs) self.assertEqual(r1, r2) def assertEqualException(self, func, call_param_string, locs=None): @@ -1399,8 +1404,9 @@ class TestGetcallargsFunctions(unittest.TestCase): else: self.fail('Exception not raised') try: - eval('inspect.getcallargs(func, %s)' % call_param_string, None, - locs) + with self.assertWarns(DeprecationWarning): + eval('inspect.getcallargs(func, %s)' % call_param_string, None, + locs) except Exception as e: ex2 = e else: @@ -1558,14 +1564,16 @@ class TestGetcallargsFunctions(unittest.TestCase): def f5(*, a): pass with self.assertRaisesRegex(TypeError, 'missing 1 required keyword-only'): - inspect.getcallargs(f5) + with self.assertWarns(DeprecationWarning): + inspect.getcallargs(f5) # issue20817: def f6(a, b, c): pass with self.assertRaisesRegex(TypeError, "'a', 'b' and 'c'"): - inspect.getcallargs(f6) + with self.assertWarns(DeprecationWarning): + inspect.getcallargs(f6) # bpo-33197 with self.assertRaisesRegex(ValueError, diff --git a/Lib/unittest/test/testmock/testhelpers.py b/Lib/unittest/test/testmock/testhelpers.py index 0d03108..e321976 100644 --- a/Lib/unittest/test/testmock/testhelpers.py +++ b/Lib/unittest/test/testmock/testhelpers.py @@ -920,7 +920,7 @@ class SpecSignatureTest(unittest.TestCase): mock(1, 2) mock(x=1, y=2) - self.assertEqual(inspect.getfullargspec(mock), inspect.getfullargspec(myfunc)) + self.assertEqual(inspect.signature(mock), inspect.signature(myfunc)) self.assertEqual(mock.mock_calls, [call(1, 2), call(x=1, y=2)]) self.assertRaises(TypeError, mock, 1) @@ -934,7 +934,7 @@ class SpecSignatureTest(unittest.TestCase): mock(1, 2, c=3) mock(1, c=3) - self.assertEqual(inspect.getfullargspec(mock), inspect.getfullargspec(foo)) + self.assertEqual(inspect.signature(mock), inspect.signature(foo)) self.assertEqual(mock.mock_calls, [call(1, 2, c=3), call(1, c=3)]) self.assertRaises(TypeError, mock, 1) self.assertRaises(TypeError, mock, 1, 2, 3, c=4) diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-04-29-23-30-21.bpo-36751.3NCRbm.rst b/Misc/NEWS.d/next/Core and Builtins/2019-04-29-23-30-21.bpo-36751.3NCRbm.rst new file mode 100644 index 0000000..5b16aaa --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-04-29-23-30-21.bpo-36751.3NCRbm.rst @@ -0,0 +1,3 @@ +The :func:`~inspect.getfullargspec` function in the :mod:`inspect` module is +deprecated in favor of the :func:`inspect.signature` API. Contributed by +Pablo Galindo. |