Skip to content

Commit d5d2b45

Browse files
authored
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
1 parent 81c5a90 commit d5d2b45

File tree

6 files changed

+74
-61
lines changed

6 files changed

+74
-61
lines changed

Doc/library/inspect.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,11 @@ Classes and functions
948948
APIs. This function is retained primarily for use in code that needs to
949949
maintain compatibility with the Python 2 ``inspect`` module API.
950950

951+
.. deprecated:: 3.8
952+
Use :func:`signature` and
953+
:ref:`Signature Object <inspect-signature-object>`, which provide a
954+
better introspecting API for callables.
955+
951956
.. versionchanged:: 3.4
952957
This function is now based on :func:`signature`, but still ignores
953958
``__wrapped__`` attributes and includes the already bound first

Doc/whatsnew/3.8.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,10 @@ Deprecated
726726
<positional-only_parameter>`.
727727
(Contributed by Serhiy Storchaka in :issue:`36492`.)
728728

729+
* The function :func:`~inspect.getfullargspec` in the :mod:`inspect`
730+
module is deprecated in favor of the :func:`inspect.signature`
731+
API. (Contributed by Pablo Galindo in :issue:`36751`.)
732+
729733

730734
API and Feature Removals
731735
========================

Lib/inspect.py

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,16 +1081,15 @@ def getargspec(func):
10811081
warnings.warn("inspect.getargspec() is deprecated since Python 3.0, "
10821082
"use inspect.signature() or inspect.getfullargspec()",
10831083
DeprecationWarning, stacklevel=2)
1084-
args, varargs, varkw, defaults, posonlyargs, kwonlyargs, \
1085-
kwonlydefaults, ann = getfullargspec(func)
1086-
if posonlyargs or kwonlyargs or ann:
1087-
raise ValueError("Function has positional-only, keyword-only parameters"
1088-
" or annotations, use getfullargspec() API which can"
1089-
" support them")
1084+
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \
1085+
getfullargspec(func)
1086+
if kwonlyargs or ann:
1087+
raise ValueError("Function has keyword-only parameters or annotations"
1088+
", use inspect.signature() API which can support them")
10901089
return ArgSpec(args, varargs, varkw, defaults)
10911090

10921091
FullArgSpec = namedtuple('FullArgSpec',
1093-
'args, varargs, varkw, defaults, posonlyargs, kwonlyargs, kwonlydefaults, annotations')
1092+
'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations')
10941093

10951094
def getfullargspec(func):
10961095
"""Get the names and default values of a callable object's parameters.
@@ -1104,11 +1103,16 @@ def getfullargspec(func):
11041103
'kwonlydefaults' is a dictionary mapping names from kwonlyargs to defaults.
11051104
'annotations' is a dictionary mapping parameter names to annotations.
11061105
1106+
.. deprecated:: 3.8
1107+
Use inspect.signature() instead of inspect.getfullargspec().
1108+
11071109
Notable differences from inspect.signature():
11081110
- the "self" parameter is always reported, even for bound methods
11091111
- wrapper chains defined by __wrapped__ *not* unwrapped automatically
11101112
"""
11111113

1114+
warnings.warn("Use inspect.signature() instead of inspect.getfullargspec()",
1115+
DeprecationWarning)
11121116
try:
11131117
# Re: `skip_bound_arg=False`
11141118
#
@@ -1182,8 +1186,8 @@ def getfullargspec(func):
11821186
# compatibility with 'func.__defaults__'
11831187
defaults = None
11841188

1185-
return FullArgSpec(args, varargs, varkw, defaults,
1186-
posonlyargs, kwonlyargs, kwdefaults, annotations)
1189+
return FullArgSpec(posonlyargs + args, varargs, varkw, defaults,
1190+
kwonlyargs, kwdefaults, annotations)
11871191

11881192

11891193
ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals')
@@ -1214,8 +1218,7 @@ def _formatannotation(annotation):
12141218
return _formatannotation
12151219

12161220
def formatargspec(args, varargs=None, varkw=None, defaults=None,
1217-
posonlyargs=(), kwonlyargs=(), kwonlydefaults={},
1218-
annotations={},
1221+
kwonlyargs=(), kwonlydefaults={}, annotations={},
12191222
formatarg=str,
12201223
formatvarargs=lambda name: '*' + name,
12211224
formatvarkw=lambda name: '**' + name,
@@ -1248,17 +1251,12 @@ def formatargandannotation(arg):
12481251
return result
12491252
specs = []
12501253
if defaults:
1251-
firstdefault = len(posonlyargs) + len(args) - len(defaults)
1252-
posonly_left = len(posonlyargs)
1253-
for i, arg in enumerate([*posonlyargs, *args]):
1254+
firstdefault = len(args) - len(defaults)
1255+
for i, arg in enumerate(args):
12541256
spec = formatargandannotation(arg)
12551257
if defaults and i >= firstdefault:
12561258
spec = spec + formatvalue(defaults[i - firstdefault])
12571259
specs.append(spec)
1258-
posonly_left -= 1
1259-
if posonlyargs and posonly_left == 0:
1260-
specs.append('/')
1261-
12621260
if varargs is not None:
12631261
specs.append(formatvarargs(formatargandannotation(varargs)))
12641262
else:
@@ -1346,8 +1344,7 @@ def getcallargs(*func_and_positional, **named):
13461344
func = func_and_positional[0]
13471345
positional = func_and_positional[1:]
13481346
spec = getfullargspec(func)
1349-
(args, varargs, varkw, defaults, posonlyargs,
1350-
kwonlyargs, kwonlydefaults, ann) = spec
1347+
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec
13511348
f_name = func.__name__
13521349
arg2value = {}
13531350

@@ -1356,16 +1353,12 @@ def getcallargs(*func_and_positional, **named):
13561353
# implicit 'self' (or 'cls' for classmethods) argument
13571354
positional = (func.__self__,) + positional
13581355
num_pos = len(positional)
1359-
num_posonlyargs = len(posonlyargs)
13601356
num_args = len(args)
13611357
num_defaults = len(defaults) if defaults else 0
13621358

1363-
n = min(num_pos, num_posonlyargs)
1364-
for i in range(num_posonlyargs):
1365-
arg2value[posonlyargs[i]] = positional[i]
13661359
n = min(num_pos, num_args)
13671360
for i in range(n):
1368-
arg2value[args[i]] = positional[num_posonlyargs+i]
1361+
arg2value[args[i]] = positional[i]
13691362
if varargs:
13701363
arg2value[varargs] = tuple(positional[n:])
13711364
possible_kwargs = set(args + kwonlyargs)

Lib/test/test_inspect.py

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -766,28 +766,29 @@ def assertFullArgSpecEquals(self, routine, args_e, varargs_e=None,
766766
posonlyargs_e=[], kwonlyargs_e=[],
767767
kwonlydefaults_e=None,
768768
ann_e={}, formatted=None):
769-
args, varargs, varkw, defaults, posonlyargs, kwonlyargs, kwonlydefaults, ann = \
770-
inspect.getfullargspec(routine)
769+
with self.assertWarns(DeprecationWarning):
770+
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \
771+
inspect.getfullargspec(routine)
771772
self.assertEqual(args, args_e)
772773
self.assertEqual(varargs, varargs_e)
773774
self.assertEqual(varkw, varkw_e)
774775
self.assertEqual(defaults, defaults_e)
775-
self.assertEqual(posonlyargs, posonlyargs_e)
776776
self.assertEqual(kwonlyargs, kwonlyargs_e)
777777
self.assertEqual(kwonlydefaults, kwonlydefaults_e)
778778
self.assertEqual(ann, ann_e)
779779
if formatted is not None:
780780
with self.assertWarns(DeprecationWarning):
781781
self.assertEqual(inspect.formatargspec(args, varargs, varkw, defaults,
782-
posonlyargs, kwonlyargs,
783-
kwonlydefaults, ann),
782+
kwonlyargs, kwonlydefaults, ann),
784783
formatted)
785784

786785
def test_getargspec(self):
787786
self.assertArgSpecEquals(mod.eggs, ['x', 'y'], formatted='(x, y)')
788787

789-
self.assertRaises(ValueError, self.assertArgSpecEquals,
790-
mod.spam, [])
788+
self.assertArgSpecEquals(mod.spam,
789+
['a', 'b', 'c', 'd', 'e', 'f'],
790+
'g', 'h', (3, 4, 5),
791+
'(a, b, c, d=3, e=4, f=5, *g, **h)')
791792

792793
self.assertRaises(ValueError, self.assertArgSpecEquals,
793794
mod2.keyworded, [])
@@ -811,25 +812,22 @@ def test_getfullargspec(self):
811812
kwonlyargs_e=['arg'],
812813
formatted='(*, arg)')
813814

814-
self.assertFullArgSpecEquals(mod2.all_markers, ['c', 'd'],
815-
posonlyargs_e=['a', 'b'],
815+
self.assertFullArgSpecEquals(mod2.all_markers, ['a', 'b', 'c', 'd'],
816816
kwonlyargs_e=['e', 'f'],
817-
formatted='(a, b, /, c, d, *, e, f)')
817+
formatted='(a, b, c, d, *, e, f)')
818818

819819
self.assertFullArgSpecEquals(mod2.all_markers_with_args_and_kwargs,
820-
['c', 'd'],
821-
posonlyargs_e=['a', 'b'],
820+
['a', 'b', 'c', 'd'],
822821
varargs_e='args',
823822
varkw_e='kwargs',
824823
kwonlyargs_e=['e', 'f'],
825-
formatted='(a, b, /, c, d, *args, e, f, **kwargs)')
824+
formatted='(a, b, c, d, *args, e, f, **kwargs)')
826825

827-
self.assertFullArgSpecEquals(mod2.all_markers_with_defaults, ['c', 'd'],
826+
self.assertFullArgSpecEquals(mod2.all_markers_with_defaults, ['a', 'b', 'c', 'd'],
828827
defaults_e=(1,2,3),
829-
posonlyargs_e=['a', 'b'],
830828
kwonlyargs_e=['e', 'f'],
831829
kwonlydefaults_e={'e': 4, 'f': 5},
832-
formatted='(a, b=1, /, c=2, d=3, *, e=4, f=5)')
830+
formatted='(a, b=1, c=2, d=3, *, e=4, f=5)')
833831

834832
def test_argspec_api_ignores_wrapped(self):
835833
# Issue 20684: low level introspection API must ignore __wrapped__
@@ -877,25 +875,27 @@ def test():
877875
spam_param = inspect.Parameter('spam', inspect.Parameter.POSITIONAL_ONLY)
878876
test.__signature__ = inspect.Signature(parameters=(spam_param,))
879877

880-
self.assertFullArgSpecEquals(test, [], posonlyargs_e=['spam'], formatted='(spam, /)')
878+
self.assertFullArgSpecEquals(test, ['spam'], formatted='(spam)')
881879

882880
def test_getfullargspec_signature_annos(self):
883881
def test(a:'spam') -> 'ham': pass
884-
spec = inspect.getfullargspec(test)
882+
with self.assertWarns(DeprecationWarning):
883+
spec = inspect.getfullargspec(test)
885884
self.assertEqual(test.__annotations__, spec.annotations)
886885

887886
def test(): pass
888-
spec = inspect.getfullargspec(test)
887+
with self.assertWarns(DeprecationWarning):
888+
spec = inspect.getfullargspec(test)
889889
self.assertEqual(test.__annotations__, spec.annotations)
890890

891891
@unittest.skipIf(MISSING_C_DOCSTRINGS,
892892
"Signature information for builtins requires docstrings")
893893
def test_getfullargspec_builtin_methods(self):
894-
self.assertFullArgSpecEquals(_pickle.Pickler.dump, [],
895-
posonlyargs_e=['self', 'obj'], formatted='(self, obj, /)')
894+
self.assertFullArgSpecEquals(_pickle.Pickler.dump, ['self', 'obj'],
895+
formatted='(self, obj)')
896896

897-
self.assertFullArgSpecEquals(_pickle.Pickler(io.BytesIO()).dump, [],
898-
posonlyargs_e=['self', 'obj'], formatted='(self, obj, /)')
897+
self.assertFullArgSpecEquals(_pickle.Pickler(io.BytesIO()).dump, ['self', 'obj'],
898+
formatted='(self, obj)')
899899

900900
self.assertFullArgSpecEquals(
901901
os.stat,
@@ -910,7 +910,8 @@ def test_getfullargspec_builtin_methods(self):
910910
def test_getfullargspec_builtin_func(self):
911911
import _testcapi
912912
builtin = _testcapi.docstring_with_signature_with_defaults
913-
spec = inspect.getfullargspec(builtin)
913+
with self.assertWarns(DeprecationWarning):
914+
spec = inspect.getfullargspec(builtin)
914915
self.assertEqual(spec.defaults[0], 'avocado')
915916

916917
@cpython_only
@@ -919,17 +920,20 @@ def test_getfullargspec_builtin_func(self):
919920
def test_getfullargspec_builtin_func_no_signature(self):
920921
import _testcapi
921922
builtin = _testcapi.docstring_no_signature
922-
with self.assertRaises(TypeError):
923-
inspect.getfullargspec(builtin)
923+
with self.assertWarns(DeprecationWarning):
924+
with self.assertRaises(TypeError):
925+
inspect.getfullargspec(builtin)
924926

925927
def test_getfullargspec_definition_order_preserved_on_kwonly(self):
926928
for fn in signatures_with_lexicographic_keyword_only_parameters():
927-
signature = inspect.getfullargspec(fn)
929+
with self.assertWarns(DeprecationWarning):
930+
signature = inspect.getfullargspec(fn)
928931
l = list(signature.kwonlyargs)
929932
sorted_l = sorted(l)
930933
self.assertTrue(l)
931934
self.assertEqual(l, sorted_l)
932-
signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn)
935+
with self.assertWarns(DeprecationWarning):
936+
signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn)
933937
l = list(signature.kwonlyargs)
934938
self.assertEqual(l, unsorted_keyword_only_parameters)
935939

@@ -1386,8 +1390,9 @@ class TestGetcallargsFunctions(unittest.TestCase):
13861390
def assertEqualCallArgs(self, func, call_params_string, locs=None):
13871391
locs = dict(locs or {}, func=func)
13881392
r1 = eval('func(%s)' % call_params_string, None, locs)
1389-
r2 = eval('inspect.getcallargs(func, %s)' % call_params_string, None,
1390-
locs)
1393+
with self.assertWarns(DeprecationWarning):
1394+
r2 = eval('inspect.getcallargs(func, %s)' % call_params_string, None,
1395+
locs)
13911396
self.assertEqual(r1, r2)
13921397

13931398
def assertEqualException(self, func, call_param_string, locs=None):
@@ -1399,8 +1404,9 @@ def assertEqualException(self, func, call_param_string, locs=None):
13991404
else:
14001405
self.fail('Exception not raised')
14011406
try:
1402-
eval('inspect.getcallargs(func, %s)' % call_param_string, None,
1403-
locs)
1407+
with self.assertWarns(DeprecationWarning):
1408+
eval('inspect.getcallargs(func, %s)' % call_param_string, None,
1409+
locs)
14041410
except Exception as e:
14051411
ex2 = e
14061412
else:
@@ -1558,14 +1564,16 @@ def test_errors(self):
15581564
def f5(*, a): pass
15591565
with self.assertRaisesRegex(TypeError,
15601566
'missing 1 required keyword-only'):
1561-
inspect.getcallargs(f5)
1567+
with self.assertWarns(DeprecationWarning):
1568+
inspect.getcallargs(f5)
15621569

15631570

15641571
# issue20817:
15651572
def f6(a, b, c):
15661573
pass
15671574
with self.assertRaisesRegex(TypeError, "'a', 'b' and 'c'"):
1568-
inspect.getcallargs(f6)
1575+
with self.assertWarns(DeprecationWarning):
1576+
inspect.getcallargs(f6)
15691577

15701578
# bpo-33197
15711579
with self.assertRaisesRegex(ValueError,

Lib/unittest/test/testmock/testhelpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,7 @@ def myfunc(x, y):
920920
mock(1, 2)
921921
mock(x=1, y=2)
922922

923-
self.assertEqual(inspect.getfullargspec(mock), inspect.getfullargspec(myfunc))
923+
self.assertEqual(inspect.signature(mock), inspect.signature(myfunc))
924924
self.assertEqual(mock.mock_calls, [call(1, 2), call(x=1, y=2)])
925925
self.assertRaises(TypeError, mock, 1)
926926

@@ -934,7 +934,7 @@ def foo(a: int, b: int=10, *, c:int) -> int:
934934
mock(1, 2, c=3)
935935
mock(1, c=3)
936936

937-
self.assertEqual(inspect.getfullargspec(mock), inspect.getfullargspec(foo))
937+
self.assertEqual(inspect.signature(mock), inspect.signature(foo))
938938
self.assertEqual(mock.mock_calls, [call(1, 2, c=3), call(1, c=3)])
939939
self.assertRaises(TypeError, mock, 1)
940940
self.assertRaises(TypeError, mock, 1, 2, 3, c=4)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The :func:`~inspect.getfullargspec` function in the :mod:`inspect` module is
2+
deprecated in favor of the :func:`inspect.signature` API. Contributed by
3+
Pablo Galindo.

0 commit comments

Comments
 (0)