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
This commit is contained in:
Pablo Galindo 2019-04-30 02:01:14 +01:00 committed by GitHub
parent 81c5a90595
commit d5d2b45469
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 74 additions and 61 deletions

View file

@ -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

View file

@ -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
======================== ========================

View file

@ -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):
'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):
# 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):
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,
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):
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):
# 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)

View file

@ -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):
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):
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):
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):
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):
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):
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):
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,

View file

@ -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):
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)

View file

@ -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.