[Python-checkins] bpo-36751: Deprecate getfullargspec and report positional-only args as regular args (GH-13016)

Pablo Galindo webhook-mailer at python.org
Mon Apr 29 21:01:17 EDT 2019


https://github.com/python/cpython/commit/d5d2b4546939b98244708e5bb0cfccd55b99d244
commit: d5d2b4546939b98244708e5bb0cfccd55b99d244
branch: master
author: Pablo Galindo <Pablogsal at gmail.com>
committer: GitHub <noreply at github.com>
date: 2019-04-30T02:01:14+01:00
summary:

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

files:
A Misc/NEWS.d/next/Core and Builtins/2019-04-29-23-30-21.bpo-36751.3NCRbm.rst
M Doc/library/inspect.rst
M Doc/whatsnew/3.8.rst
M Lib/inspect.py
M Lib/test/test_inspect.py
M Lib/unittest/test/testmock/testhelpers.py

diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index 81824ddc1e54..d12f122a57b5 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 ac57335221c4..bbc55ddd6341 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 3201e794fb37..fffca22357a9 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 _formatannotation(annotation):
     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 formatargandannotation(arg):
         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 06f8d69a579f..3c825b00e5e4 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -766,28 +766,29 @@ def assertFullArgSpecEquals(self, routine, args_e, varargs_e=None,
                                     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 @@ def test_getfullargspec(self):
                                      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 @@ def test():
         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 @@ 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.assertWarns(DeprecationWarning):
+            spec = inspect.getfullargspec(builtin)
         self.assertEqual(spec.defaults[0], 'avocado')
 
     @cpython_only
@@ -919,17 +920,20 @@ 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):
-            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 @@ def assertEqualException(self, func, call_param_string, locs=None):
         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 @@ def test_errors(self):
         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 0d03108aca55..e321976aeb9f 100644
--- a/Lib/unittest/test/testmock/testhelpers.py
+++ b/Lib/unittest/test/testmock/testhelpers.py
@@ -920,7 +920,7 @@ def myfunc(x, y):
         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 @@ def foo(a: int, b: int=10, *, c:int) -> int:
         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 000000000000..5b16aaa0b0c3
--- /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.



More information about the Python-checkins mailing list