Commit d5d2b454 authored by Pablo Galindo's avatar Pablo Galindo Committed by GitHub

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
parent 81c5a905
...@@ -948,6 +948,11 @@ Classes and functions ...@@ -948,6 +948,11 @@ Classes and functions
APIs. This function is retained primarily for use in code that needs to APIs. This function is retained primarily for use in code that needs to
maintain compatibility with the Python 2 ``inspect`` module API. 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 .. versionchanged:: 3.4
This function is now based on :func:`signature`, but still ignores This function is now based on :func:`signature`, but still ignores
``__wrapped__`` attributes and includes the already bound first ``__wrapped__`` attributes and includes the already bound first
......
...@@ -726,6 +726,10 @@ Deprecated ...@@ -726,6 +726,10 @@ Deprecated
<positional-only_parameter>`. <positional-only_parameter>`.
(Contributed by Serhiy Storchaka in :issue:`36492`.) (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 API and Feature Removals
======================== ========================
......
...@@ -1081,16 +1081,15 @@ def getargspec(func): ...@@ -1081,16 +1081,15 @@ def getargspec(func):
warnings.warn("inspect.getargspec() is deprecated since Python 3.0, " warnings.warn("inspect.getargspec() is deprecated since Python 3.0, "
"use inspect.signature() or inspect.getfullargspec()", "use inspect.signature() or inspect.getfullargspec()",
DeprecationWarning, stacklevel=2) DeprecationWarning, stacklevel=2)
args, varargs, varkw, defaults, posonlyargs, kwonlyargs, \ args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \
kwonlydefaults, ann = getfullargspec(func) getfullargspec(func)
if posonlyargs or kwonlyargs or ann: if kwonlyargs or ann:
raise ValueError("Function has positional-only, keyword-only parameters" raise ValueError("Function has keyword-only parameters or annotations"
" or annotations, use getfullargspec() API which can" ", use inspect.signature() API which can support them")
" support them")
return ArgSpec(args, varargs, varkw, defaults) return ArgSpec(args, varargs, varkw, defaults)
FullArgSpec = namedtuple('FullArgSpec', FullArgSpec = namedtuple('FullArgSpec',
'args, varargs, varkw, defaults, posonlyargs, kwonlyargs, kwonlydefaults, annotations') 'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations')
def getfullargspec(func): def getfullargspec(func):
"""Get the names and default values of a callable object's parameters. """Get the names and default values of a callable object's parameters.
...@@ -1104,11 +1103,16 @@ def getfullargspec(func): ...@@ -1104,11 +1103,16 @@ def getfullargspec(func):
'kwonlydefaults' is a dictionary mapping names from kwonlyargs to defaults. 'kwonlydefaults' is a dictionary mapping names from kwonlyargs to defaults.
'annotations' is a dictionary mapping parameter names to annotations. 'annotations' is a dictionary mapping parameter names to annotations.
.. deprecated:: 3.8
Use inspect.signature() instead of inspect.getfullargspec().
Notable differences from inspect.signature(): Notable differences from inspect.signature():
- the "self" parameter is always reported, even for bound methods - the "self" parameter is always reported, even for bound methods
- wrapper chains defined by __wrapped__ *not* unwrapped automatically - wrapper chains defined by __wrapped__ *not* unwrapped automatically
""" """
warnings.warn("Use inspect.signature() instead of inspect.getfullargspec()",
DeprecationWarning)
try: try:
# Re: `skip_bound_arg=False` # Re: `skip_bound_arg=False`
# #
...@@ -1182,8 +1186,8 @@ def getfullargspec(func): ...@@ -1182,8 +1186,8 @@ def getfullargspec(func):
# compatibility with 'func.__defaults__' # compatibility with 'func.__defaults__'
defaults = None defaults = None
return FullArgSpec(args, varargs, varkw, defaults, return FullArgSpec(posonlyargs + args, varargs, varkw, defaults,
posonlyargs, kwonlyargs, kwdefaults, annotations) kwonlyargs, kwdefaults, annotations)
ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals') ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals')
...@@ -1214,8 +1218,7 @@ def formatannotationrelativeto(object): ...@@ -1214,8 +1218,7 @@ def formatannotationrelativeto(object):
return _formatannotation return _formatannotation
def formatargspec(args, varargs=None, varkw=None, defaults=None, def formatargspec(args, varargs=None, varkw=None, defaults=None,
posonlyargs=(), kwonlyargs=(), kwonlydefaults={}, kwonlyargs=(), kwonlydefaults={}, annotations={},
annotations={},
formatarg=str, formatarg=str,
formatvarargs=lambda name: '*' + name, formatvarargs=lambda name: '*' + name,
formatvarkw=lambda name: '**' + name, formatvarkw=lambda name: '**' + name,
...@@ -1248,17 +1251,12 @@ def formatargspec(args, varargs=None, varkw=None, defaults=None, ...@@ -1248,17 +1251,12 @@ def formatargspec(args, varargs=None, varkw=None, defaults=None,
return result return result
specs = [] specs = []
if defaults: if defaults:
firstdefault = len(posonlyargs) + len(args) - len(defaults) firstdefault = len(args) - len(defaults)
posonly_left = len(posonlyargs) for i, arg in enumerate(args):
for i, arg in enumerate([*posonlyargs, *args]):
spec = formatargandannotation(arg) spec = formatargandannotation(arg)
if defaults and i >= firstdefault: if defaults and i >= firstdefault:
spec = spec + formatvalue(defaults[i - firstdefault]) spec = spec + formatvalue(defaults[i - firstdefault])
specs.append(spec) specs.append(spec)
posonly_left -= 1
if posonlyargs and posonly_left == 0:
specs.append('/')
if varargs is not None: if varargs is not None:
specs.append(formatvarargs(formatargandannotation(varargs))) specs.append(formatvarargs(formatargandannotation(varargs)))
else: else:
...@@ -1346,8 +1344,7 @@ def getcallargs(*func_and_positional, **named): ...@@ -1346,8 +1344,7 @@ def getcallargs(*func_and_positional, **named):
func = func_and_positional[0] func = func_and_positional[0]
positional = func_and_positional[1:] positional = func_and_positional[1:]
spec = getfullargspec(func) spec = getfullargspec(func)
(args, varargs, varkw, defaults, posonlyargs, args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec
kwonlyargs, kwonlydefaults, ann) = spec
f_name = func.__name__ f_name = func.__name__
arg2value = {} arg2value = {}
...@@ -1356,16 +1353,12 @@ def getcallargs(*func_and_positional, **named): ...@@ -1356,16 +1353,12 @@ def getcallargs(*func_and_positional, **named):
# implicit 'self' (or 'cls' for classmethods) argument # implicit 'self' (or 'cls' for classmethods) argument
positional = (func.__self__,) + positional positional = (func.__self__,) + positional
num_pos = len(positional) num_pos = len(positional)
num_posonlyargs = len(posonlyargs)
num_args = len(args) num_args = len(args)
num_defaults = len(defaults) if defaults else 0 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) n = min(num_pos, num_args)
for i in range(n): for i in range(n):
arg2value[args[i]] = positional[num_posonlyargs+i] arg2value[args[i]] = positional[i]
if varargs: if varargs:
arg2value[varargs] = tuple(positional[n:]) arg2value[varargs] = tuple(positional[n:])
possible_kwargs = set(args + kwonlyargs) possible_kwargs = set(args + kwonlyargs)
......
...@@ -766,28 +766,29 @@ class TestClassesAndFunctions(unittest.TestCase): ...@@ -766,28 +766,29 @@ class TestClassesAndFunctions(unittest.TestCase):
posonlyargs_e=[], kwonlyargs_e=[], posonlyargs_e=[], kwonlyargs_e=[],
kwonlydefaults_e=None, kwonlydefaults_e=None,
ann_e={}, formatted=None): ann_e={}, formatted=None):
args, varargs, varkw, defaults, posonlyargs, kwonlyargs, kwonlydefaults, ann = \ with self.assertWarns(DeprecationWarning):
inspect.getfullargspec(routine) args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \
inspect.getfullargspec(routine)
self.assertEqual(args, args_e) self.assertEqual(args, args_e)
self.assertEqual(varargs, varargs_e) self.assertEqual(varargs, varargs_e)
self.assertEqual(varkw, varkw_e) self.assertEqual(varkw, varkw_e)
self.assertEqual(defaults, defaults_e) self.assertEqual(defaults, defaults_e)
self.assertEqual(posonlyargs, posonlyargs_e)
self.assertEqual(kwonlyargs, kwonlyargs_e) self.assertEqual(kwonlyargs, kwonlyargs_e)
self.assertEqual(kwonlydefaults, kwonlydefaults_e) self.assertEqual(kwonlydefaults, kwonlydefaults_e)
self.assertEqual(ann, ann_e) self.assertEqual(ann, ann_e)
if formatted is not None: if formatted is not None:
with self.assertWarns(DeprecationWarning): with self.assertWarns(DeprecationWarning):
self.assertEqual(inspect.formatargspec(args, varargs, varkw, defaults, self.assertEqual(inspect.formatargspec(args, varargs, varkw, defaults,
posonlyargs, kwonlyargs, kwonlyargs, kwonlydefaults, ann),
kwonlydefaults, ann),
formatted) formatted)
def test_getargspec(self): def test_getargspec(self):
self.assertArgSpecEquals(mod.eggs, ['x', 'y'], formatted='(x, y)') self.assertArgSpecEquals(mod.eggs, ['x', 'y'], formatted='(x, y)')
self.assertRaises(ValueError, self.assertArgSpecEquals, self.assertArgSpecEquals(mod.spam,
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, self.assertRaises(ValueError, self.assertArgSpecEquals,
mod2.keyworded, []) mod2.keyworded, [])
...@@ -811,25 +812,22 @@ class TestClassesAndFunctions(unittest.TestCase): ...@@ -811,25 +812,22 @@ class TestClassesAndFunctions(unittest.TestCase):
kwonlyargs_e=['arg'], kwonlyargs_e=['arg'],
formatted='(*, arg)') formatted='(*, arg)')
self.assertFullArgSpecEquals(mod2.all_markers, ['c', 'd'], self.assertFullArgSpecEquals(mod2.all_markers, ['a', 'b', 'c', 'd'],
posonlyargs_e=['a', 'b'],
kwonlyargs_e=['e', 'f'], 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, self.assertFullArgSpecEquals(mod2.all_markers_with_args_and_kwargs,
['c', 'd'], ['a', 'b', 'c', 'd'],
posonlyargs_e=['a', 'b'],
varargs_e='args', varargs_e='args',
varkw_e='kwargs', varkw_e='kwargs',
kwonlyargs_e=['e', 'f'], 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), defaults_e=(1,2,3),
posonlyargs_e=['a', 'b'],
kwonlyargs_e=['e', 'f'], kwonlyargs_e=['e', 'f'],
kwonlydefaults_e={'e': 4, 'f': 5}, 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): def test_argspec_api_ignores_wrapped(self):
# Issue 20684: low level introspection API must ignore __wrapped__ # Issue 20684: low level introspection API must ignore __wrapped__
...@@ -877,25 +875,27 @@ class TestClassesAndFunctions(unittest.TestCase): ...@@ -877,25 +875,27 @@ class TestClassesAndFunctions(unittest.TestCase):
spam_param = inspect.Parameter('spam', inspect.Parameter.POSITIONAL_ONLY) spam_param = inspect.Parameter('spam', inspect.Parameter.POSITIONAL_ONLY)
test.__signature__ = inspect.Signature(parameters=(spam_param,)) 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_getfullargspec_signature_annos(self):
def test(a:'spam') -> 'ham': pass def test(a:'spam') -> 'ham': pass
spec = inspect.getfullargspec(test) with self.assertWarns(DeprecationWarning):
spec = inspect.getfullargspec(test)
self.assertEqual(test.__annotations__, spec.annotations) self.assertEqual(test.__annotations__, spec.annotations)
def test(): pass def test(): pass
spec = inspect.getfullargspec(test) with self.assertWarns(DeprecationWarning):
spec = inspect.getfullargspec(test)
self.assertEqual(test.__annotations__, spec.annotations) self.assertEqual(test.__annotations__, spec.annotations)
@unittest.skipIf(MISSING_C_DOCSTRINGS, @unittest.skipIf(MISSING_C_DOCSTRINGS,
"Signature information for builtins requires docstrings") "Signature information for builtins requires docstrings")
def test_getfullargspec_builtin_methods(self): def test_getfullargspec_builtin_methods(self):
self.assertFullArgSpecEquals(_pickle.Pickler.dump, [], self.assertFullArgSpecEquals(_pickle.Pickler.dump, ['self', 'obj'],
posonlyargs_e=['self', 'obj'], formatted='(self, obj, /)') formatted='(self, obj)')
self.assertFullArgSpecEquals(_pickle.Pickler(io.BytesIO()).dump, [], self.assertFullArgSpecEquals(_pickle.Pickler(io.BytesIO()).dump, ['self', 'obj'],
posonlyargs_e=['self', 'obj'], formatted='(self, obj, /)') formatted='(self, obj)')
self.assertFullArgSpecEquals( self.assertFullArgSpecEquals(
os.stat, os.stat,
...@@ -910,7 +910,8 @@ class TestClassesAndFunctions(unittest.TestCase): ...@@ -910,7 +910,8 @@ class TestClassesAndFunctions(unittest.TestCase):
def test_getfullargspec_builtin_func(self): def test_getfullargspec_builtin_func(self):
import _testcapi import _testcapi
builtin = _testcapi.docstring_with_signature_with_defaults 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') self.assertEqual(spec.defaults[0], 'avocado')
@cpython_only @cpython_only
...@@ -919,17 +920,20 @@ class TestClassesAndFunctions(unittest.TestCase): ...@@ -919,17 +920,20 @@ class TestClassesAndFunctions(unittest.TestCase):
def test_getfullargspec_builtin_func_no_signature(self): def test_getfullargspec_builtin_func_no_signature(self):
import _testcapi import _testcapi
builtin = _testcapi.docstring_no_signature builtin = _testcapi.docstring_no_signature
with self.assertRaises(TypeError): with self.assertWarns(DeprecationWarning):
inspect.getfullargspec(builtin) with self.assertRaises(TypeError):
inspect.getfullargspec(builtin)
def test_getfullargspec_definition_order_preserved_on_kwonly(self): def test_getfullargspec_definition_order_preserved_on_kwonly(self):
for fn in signatures_with_lexicographic_keyword_only_parameters(): 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) l = list(signature.kwonlyargs)
sorted_l = sorted(l) sorted_l = sorted(l)
self.assertTrue(l) self.assertTrue(l)
self.assertEqual(l, sorted_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) l = list(signature.kwonlyargs)
self.assertEqual(l, unsorted_keyword_only_parameters) self.assertEqual(l, unsorted_keyword_only_parameters)
...@@ -1386,8 +1390,9 @@ class TestGetcallargsFunctions(unittest.TestCase): ...@@ -1386,8 +1390,9 @@ class TestGetcallargsFunctions(unittest.TestCase):
def assertEqualCallArgs(self, func, call_params_string, locs=None): def assertEqualCallArgs(self, func, call_params_string, locs=None):
locs = dict(locs or {}, func=func) locs = dict(locs or {}, func=func)
r1 = eval('func(%s)' % call_params_string, None, locs) r1 = eval('func(%s)' % call_params_string, None, locs)
r2 = eval('inspect.getcallargs(func, %s)' % call_params_string, None, with self.assertWarns(DeprecationWarning):
locs) r2 = eval('inspect.getcallargs(func, %s)' % call_params_string, None,
locs)
self.assertEqual(r1, r2) self.assertEqual(r1, r2)
def assertEqualException(self, func, call_param_string, locs=None): def assertEqualException(self, func, call_param_string, locs=None):
...@@ -1399,8 +1404,9 @@ class TestGetcallargsFunctions(unittest.TestCase): ...@@ -1399,8 +1404,9 @@ class TestGetcallargsFunctions(unittest.TestCase):
else: else:
self.fail('Exception not raised') self.fail('Exception not raised')
try: try:
eval('inspect.getcallargs(func, %s)' % call_param_string, None, with self.assertWarns(DeprecationWarning):
locs) eval('inspect.getcallargs(func, %s)' % call_param_string, None,
locs)
except Exception as e: except Exception as e:
ex2 = e ex2 = e
else: else:
...@@ -1558,14 +1564,16 @@ class TestGetcallargsFunctions(unittest.TestCase): ...@@ -1558,14 +1564,16 @@ class TestGetcallargsFunctions(unittest.TestCase):
def f5(*, a): pass def f5(*, a): pass
with self.assertRaisesRegex(TypeError, with self.assertRaisesRegex(TypeError,
'missing 1 required keyword-only'): 'missing 1 required keyword-only'):
inspect.getcallargs(f5) with self.assertWarns(DeprecationWarning):
inspect.getcallargs(f5)
# issue20817: # issue20817:
def f6(a, b, c): def f6(a, b, c):
pass pass
with self.assertRaisesRegex(TypeError, "'a', 'b' and 'c'"): with self.assertRaisesRegex(TypeError, "'a', 'b' and 'c'"):
inspect.getcallargs(f6) with self.assertWarns(DeprecationWarning):
inspect.getcallargs(f6)
# bpo-33197 # bpo-33197
with self.assertRaisesRegex(ValueError, with self.assertRaisesRegex(ValueError,
......
...@@ -920,7 +920,7 @@ class SpecSignatureTest(unittest.TestCase): ...@@ -920,7 +920,7 @@ class SpecSignatureTest(unittest.TestCase):
mock(1, 2) mock(1, 2)
mock(x=1, y=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.assertEqual(mock.mock_calls, [call(1, 2), call(x=1, y=2)])
self.assertRaises(TypeError, mock, 1) self.assertRaises(TypeError, mock, 1)
...@@ -934,7 +934,7 @@ class SpecSignatureTest(unittest.TestCase): ...@@ -934,7 +934,7 @@ class SpecSignatureTest(unittest.TestCase):
mock(1, 2, c=3) mock(1, 2, c=3)
mock(1, 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.assertEqual(mock.mock_calls, [call(1, 2, c=3), call(1, c=3)])
self.assertRaises(TypeError, mock, 1) self.assertRaises(TypeError, mock, 1)
self.assertRaises(TypeError, mock, 1, 2, 3, c=4) self.assertRaises(TypeError, mock, 1, 2, 3, c=4)
......
The :func:`~inspect.getfullargspec` function in the :mod:`inspect` module is
deprecated in favor of the :func:`inspect.signature` API. Contributed by
Pablo Galindo.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment