Skip to content

Commit 41c4e36

Browse files
committed
pythongh-108901: Deprecate inspect.getfullargspec and slate it for removal in 3.15
1 parent fef6fb8 commit 41c4e36

File tree

5 files changed

+113
-13
lines changed

5 files changed

+113
-13
lines changed

Doc/library/inspect.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,21 @@ Classes and functions
10461046
order of keyword-only parameters as of version 3.7, although in practice
10471047
this order had always been preserved in Python 3.
10481048

1049+
.. deprecated-removed:: 3.13, 3.15
1050+
Now all :func:`getfullargspec` differences from :class:`Signature`
1051+
can be solved by passing ``follow_wrapped=False, skip_bound_arg=False``
1052+
arguments::
1053+
1054+
from inspect import signature
1055+
1056+
signature(your_obj, follow_wrapped=False, skip_bound_arg=False)
1057+
1058+
For Python versions older than 3.13 you can use ``inspect313`` PyPI package::
1059+
1060+
from inspect313 import signature
1061+
1062+
signature(your_obj, follow_wrapped=False, skip_bound_arg=False)
1063+
10491064

10501065
.. function:: getargvalues(frame)
10511066

Doc/whatsnew/3.13.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,11 @@ Pending Removal in Python 3.15
587587
All arguments will be removed from :func:`threading.RLock` in Python 3.15.
588588
(Contributed by Nikita Sobolev in :gh:`102029`.)
589589

590+
* :func:`inspect.getfullargspec` is deprecated and slated for removal in 3.15,
591+
use :func:`inspect.signature` instead or ``inspect313`` PyPI package
592+
for older versions.
593+
(Contributed by Nikita Sobolev in :gh:`108901`.)
594+
590595
Pending Removal in Python 3.16
591596
------------------------------
592597

Lib/inspect.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,13 @@ def getfullargspec(func):
14031403
- the "self" parameter is always reported, even for bound methods
14041404
- wrapper chains defined by __wrapped__ *not* unwrapped automatically
14051405
"""
1406+
import warnings
1407+
warnings._deprecated(
1408+
"getfullargspec",
1409+
'{name!r} is deprecated since 3.13 and slated for removal in Python {remove}, '
1410+
'use `inspect.Signature` or `inspect313` for older Python versions instead',
1411+
remove=(3, 15),
1412+
)
14061413
try:
14071414
# Re: `skip_bound_arg=False`
14081415
#
@@ -1579,7 +1586,10 @@ def getcallargs(func, /, *positional, **named):
15791586
A dict is returned, with keys the function argument names (including the
15801587
names of the * and ** arguments, if any), and values the respective bound
15811588
values from 'positional' and 'named'."""
1582-
spec = getfullargspec(func)
1589+
import warnings
1590+
with warnings.catch_warnings():
1591+
warnings.simplefilter('ignore', category=DeprecationWarning)
1592+
spec = getfullargspec(func)
15831593
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec
15841594
f_name = func.__name__
15851595
arg2value = {}
@@ -3103,10 +3113,12 @@ def __init__(self, parameters=None, *, return_annotation=_empty,
31033113

31043114
@classmethod
31053115
def from_callable(cls, obj, *,
3106-
follow_wrapped=True, globals=None, locals=None, eval_str=False):
3116+
follow_wrapped=True, skip_bound_arg=True,
3117+
globals=None, locals=None, eval_str=False):
31073118
"""Constructs Signature for the given callable object."""
31083119
return _signature_from_callable(obj, sigcls=cls,
31093120
follow_wrapper_chains=follow_wrapped,
3121+
skip_bound_arg=skip_bound_arg,
31103122
globals=globals, locals=locals, eval_str=eval_str)
31113123

31123124
@property
@@ -3361,9 +3373,11 @@ def __str__(self):
33613373
return rendered
33623374

33633375

3364-
def signature(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False):
3376+
def signature(obj, *, follow_wrapped=True, skip_bound_arg=True,
3377+
globals=None, locals=None, eval_str=False):
33653378
"""Get a signature object for the passed callable."""
33663379
return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
3380+
skip_bound_arg=skip_bound_arg,
33673381
globals=globals, locals=locals, eval_str=eval_str)
33683382

33693383

Lib/test/test_inspect/test_inspect.py

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,16 @@ def attrs_wo_objs(cls):
10781078

10791079

10801080
class TestClassesAndFunctions(unittest.TestCase):
1081+
def assertDeprecated(self):
1082+
import re
1083+
return self.assertWarnsRegex(
1084+
DeprecationWarning,
1085+
re.escape(
1086+
"'getfullargspec' is deprecated since 3.13 "
1087+
"and slated for removal in Python 3.15"
1088+
),
1089+
)
1090+
10811091
def test_newstyle_mro(self):
10821092
# The same w/ new-class MRO.
10831093
class A(object): pass
@@ -1094,8 +1104,9 @@ def assertFullArgSpecEquals(self, routine, args_e, varargs_e=None,
10941104
posonlyargs_e=[], kwonlyargs_e=[],
10951105
kwonlydefaults_e=None,
10961106
ann_e={}):
1097-
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \
1098-
inspect.getfullargspec(routine)
1107+
with self.assertDeprecated():
1108+
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \
1109+
inspect.getfullargspec(routine)
10991110
self.assertEqual(args, args_e)
11001111
self.assertEqual(varargs, varargs_e)
11011112
self.assertEqual(varkw, varkw_e)
@@ -1148,11 +1159,13 @@ def test():
11481159

11491160
def test_getfullargspec_signature_annos(self):
11501161
def test(a:'spam') -> 'ham': pass
1151-
spec = inspect.getfullargspec(test)
1162+
with self.assertDeprecated():
1163+
spec = inspect.getfullargspec(test)
11521164
self.assertEqual(test.__annotations__, spec.annotations)
11531165

11541166
def test(): pass
1155-
spec = inspect.getfullargspec(test)
1167+
with self.assertDeprecated():
1168+
spec = inspect.getfullargspec(test)
11561169
self.assertEqual(test.__annotations__, spec.annotations)
11571170

11581171
@unittest.skipIf(MISSING_C_DOCSTRINGS,
@@ -1174,7 +1187,8 @@ def test_getfullargspec_builtin_methods(self):
11741187
def test_getfullargspec_builtin_func(self):
11751188
import _testcapi
11761189
builtin = _testcapi.docstring_with_signature_with_defaults
1177-
spec = inspect.getfullargspec(builtin)
1190+
with self.assertDeprecated():
1191+
spec = inspect.getfullargspec(builtin)
11781192
self.assertEqual(spec.defaults[0], 'avocado')
11791193

11801194
@cpython_only
@@ -1183,7 +1197,7 @@ def test_getfullargspec_builtin_func(self):
11831197
def test_getfullargspec_builtin_func_no_signature(self):
11841198
import _testcapi
11851199
builtin = _testcapi.docstring_no_signature
1186-
with self.assertRaises(TypeError):
1200+
with self.assertRaises(TypeError), self.assertDeprecated():
11871201
inspect.getfullargspec(builtin)
11881202

11891203
cls = _testcapi.DocStringNoSignatureTest
@@ -1224,17 +1238,22 @@ def test_getfullargspec_builtin_func_no_signature(self):
12241238
tests.append((stat.S_IMODE, meth_o))
12251239
for builtin, template in tests:
12261240
with self.subTest(builtin):
1227-
self.assertEqual(inspect.getfullargspec(builtin),
1228-
inspect.getfullargspec(template))
1241+
with self.assertDeprecated():
1242+
builtin_args = inspect.getfullargspec(builtin)
1243+
with self.assertDeprecated():
1244+
template_args = inspect.getfullargspec(template)
1245+
self.assertEqual(builtin_args, template_args)
12291246

12301247
def test_getfullargspec_definition_order_preserved_on_kwonly(self):
12311248
for fn in signatures_with_lexicographic_keyword_only_parameters():
1232-
signature = inspect.getfullargspec(fn)
1249+
with self.assertDeprecated():
1250+
signature = inspect.getfullargspec(fn)
12331251
l = list(signature.kwonlyargs)
12341252
sorted_l = sorted(l)
12351253
self.assertTrue(l)
12361254
self.assertEqual(l, sorted_l)
1237-
signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn)
1255+
with self.assertDeprecated():
1256+
signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn)
12381257
l = list(signature.kwonlyargs)
12391258
self.assertEqual(l, unsorted_keyword_only_parameters)
12401259

@@ -4136,6 +4155,49 @@ class D2(D1):
41364155

41374156
self.assertEqual(inspect.signature(D2), inspect.signature(D1))
41384157

4158+
def test_signature_as_getfullargspec_replacement(self):
4159+
def decorator(func):
4160+
@functools.wraps(func) # set `__wrapper__` attribute
4161+
def wrapper(*args, **kwargs):
4162+
return func(*args, **kwargs)
4163+
return wrapper
4164+
4165+
@decorator
4166+
def func(a: int, /, b: str = '', *, c : bool = True) -> int: ...
4167+
4168+
sig = inspect.signature(func, follow_wrapped=False, skip_bound_arg=False)
4169+
self.assertEqual(str(sig), '(*args, **kwargs) -> int')
4170+
self.assertEqual(
4171+
str(sig),
4172+
str(inspect.Signature.from_callable(func,
4173+
follow_wrapped=False,
4174+
skip_bound_arg=False)),
4175+
)
4176+
4177+
class My:
4178+
def method(self, arg: int) -> None: ...
4179+
@classmethod
4180+
def cl(cls, arg2: str) -> None: ...
4181+
4182+
sigs = {
4183+
My.method: '(self, arg: int) -> None',
4184+
My().method: '(self, arg: int) -> None',
4185+
My.cl: '(cls, arg2: str) -> None',
4186+
My().cl: '(cls, arg2: str) -> None',
4187+
}
4188+
for f, text_sig in sigs.items():
4189+
with self.subTest(f=f):
4190+
sig = inspect.signature(f,
4191+
follow_wrapped=False,
4192+
skip_bound_arg=False)
4193+
self.assertEqual(str(sig), text_sig)
4194+
self.assertEqual(
4195+
str(sig),
4196+
str(inspect.Signature.from_callable(f,
4197+
follow_wrapped=False,
4198+
skip_bound_arg=False)),
4199+
)
4200+
41394201

41404202
class TestParameterObject(unittest.TestCase):
41414203
def test_signature_parameter_kinds(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Deprecate :func:`inspect.getfullargspec` and slate it for removal in 3.15.
2+
Instead use :func:`inspect.signature` with ``follow_wrapped=False,
3+
skip_bound_arg=False`` arguments or ``inspect313`` PyPI package for Python
4+
versions older than 3.13

0 commit comments

Comments
 (0)