Skip to content

Commit 566790c

Browse files
committed
pythongh-108901: Deprecate inspect.getargs, use Signature.from_code instead
1 parent b4c8cce commit 566790c

File tree

4 files changed

+141
-35
lines changed

4 files changed

+141
-35
lines changed

Doc/library/inspect.rst

+10
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,16 @@ function.
765765
.. versionadded:: 3.10
766766
``globalns`` and ``localns`` parameters.
767767

768+
.. classmethod:: Signature.from_code(co)
769+
770+
Return a :class:`Signature` (or its subclass) object
771+
for a given :class:`code object <types.CodeType>` ``co``.
772+
773+
Since code objects do not know anything
774+
about default values or annotations,
775+
they will always be ommited from the signature.
776+
777+
.. versionadded:: 3.13
768778

769779
.. class:: Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty)
770780

Lib/inspect.py

+75-35
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,13 @@ def getargs(co):
13641364
'args' is the list of argument names. Keyword-only arguments are
13651365
appended. 'varargs' and 'varkw' are the names of the * and **
13661366
arguments or None."""
1367+
import warnings
1368+
warnings.warn(
1369+
'getargs is deprecated since Python3.13, '
1370+
'use Signature.from_code instead',
1371+
DeprecationWarning,
1372+
)
1373+
13671374
if not iscode(co):
13681375
raise TypeError('{!r} is not a code object'.format(co))
13691376

@@ -2384,52 +2391,34 @@ def p(name_node, default_node, default=empty):
23842391
return cls(parameters, return_annotation=cls.empty)
23852392

23862393

2387-
def _signature_from_builtin(cls, func, skip_bound_arg=True):
2394+
def _signature_from_code(cls,
2395+
func_code,
2396+
globals=None,
2397+
locals=None,
2398+
eval_str=False,
2399+
is_duck_function=False,
2400+
func=None):
23882401
"""Private helper function to get signature for
2389-
builtin callables.
2402+
code objects.
23902403
"""
2391-
2392-
if not _signature_is_builtin(func):
2393-
raise TypeError("{!r} is not a Python builtin "
2394-
"function".format(func))
2395-
2396-
s = getattr(func, "__text_signature__", None)
2397-
if not s:
2398-
raise ValueError("no signature found for builtin {!r}".format(func))
2399-
2400-
return _signature_fromstr(cls, func, s, skip_bound_arg)
2401-
2402-
2403-
def _signature_from_function(cls, func, skip_bound_arg=True,
2404-
globals=None, locals=None, eval_str=False):
2405-
"""Private helper: constructs Signature for the given python function."""
2406-
2407-
is_duck_function = False
2408-
if not isfunction(func):
2409-
if _signature_is_functionlike(func):
2410-
is_duck_function = True
2411-
else:
2412-
# If it's not a pure Python function, and not a duck type
2413-
# of pure function:
2414-
raise TypeError('{!r} is not a Python function'.format(func))
2415-
2416-
s = getattr(func, "__text_signature__", None)
2417-
if s:
2418-
return _signature_fromstr(cls, func, s, skip_bound_arg)
2419-
24202404
Parameter = cls._parameter_cls
24212405

24222406
# Parameter information.
2423-
func_code = func.__code__
24242407
pos_count = func_code.co_argcount
24252408
arg_names = func_code.co_varnames
24262409
posonly_count = func_code.co_posonlyargcount
24272410
positional = arg_names[:pos_count]
24282411
keyword_only_count = func_code.co_kwonlyargcount
24292412
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
2430-
annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str)
2431-
defaults = func.__defaults__
2432-
kwdefaults = func.__kwdefaults__
2413+
if func is not None:
2414+
annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str)
2415+
defaults = func.__defaults__
2416+
kwdefaults = func.__kwdefaults__
2417+
else:
2418+
# `func` can be `None` when we get a signature from just a `CodeObject`
2419+
annotations = {}
2420+
defaults = None
2421+
kwdefaults = None
24332422

24342423
if defaults:
24352424
pos_default_count = len(defaults)
@@ -2495,6 +2484,46 @@ def _signature_from_function(cls, func, skip_bound_arg=True,
24952484
__validate_parameters__=is_duck_function)
24962485

24972486

2487+
def _signature_from_builtin(cls, func, skip_bound_arg=True):
2488+
"""Private helper function to get signature for
2489+
builtin callables.
2490+
"""
2491+
2492+
if not _signature_is_builtin(func):
2493+
raise TypeError("{!r} is not a Python builtin "
2494+
"function".format(func))
2495+
2496+
s = getattr(func, "__text_signature__", None)
2497+
if not s:
2498+
raise ValueError("no signature found for builtin {!r}".format(func))
2499+
2500+
return _signature_fromstr(cls, func, s, skip_bound_arg)
2501+
2502+
2503+
def _signature_from_function(cls, func, skip_bound_arg=True,
2504+
globals=None, locals=None, eval_str=False):
2505+
"""Private helper: constructs Signature for the given python function."""
2506+
2507+
is_duck_function = False
2508+
if not isfunction(func):
2509+
if _signature_is_functionlike(func):
2510+
is_duck_function = True
2511+
else:
2512+
# If it's not a pure Python function, and not a duck type
2513+
# of pure function:
2514+
raise TypeError('{!r} is not a Python function'.format(func))
2515+
2516+
s = getattr(func, "__text_signature__", None)
2517+
if s:
2518+
return _signature_fromstr(cls, func, s, skip_bound_arg)
2519+
return _signature_from_code(cls, func.__code__,
2520+
globals=globals,
2521+
locals=locals,
2522+
eval_str=eval_str,
2523+
is_duck_function=is_duck_function,
2524+
func=func)
2525+
2526+
24982527
def _signature_from_callable(obj, *,
24992528
follow_wrapper_chains=True,
25002529
skip_bound_arg=True,
@@ -3107,6 +3136,17 @@ def from_callable(cls, obj, *,
31073136
follow_wrapper_chains=follow_wrapped,
31083137
globals=globals, locals=locals, eval_str=eval_str)
31093138

3139+
@classmethod
3140+
def from_code(cls, co):
3141+
"""Constructs Signature for the given code object.
3142+
3143+
Signatures created from code objects cannot know about annotations
3144+
or default values.
3145+
"""
3146+
if not iscode(co):
3147+
raise TypeError('{!r} is not a code object'.format(co))
3148+
return _signature_from_code(cls, co)
3149+
31103150
@property
31113151
def parameters(self):
31123152
return self._parameters

Lib/test/test_inspect.py

+54
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,25 @@ class ConcreteGrandchild(ClassExample):
435435
self.assertEqual(isabstract_checks, [True, True, False])
436436

437437

438+
class TestGetArgs(unittest.TestCase):
439+
def test_getargs_is_deprecated(self):
440+
# We are only interested in the deprecation warning,
441+
# because `getargs` is:
442+
# - undocumented
443+
# - untested
444+
# - deprecated
445+
# - has modern alternative
446+
# - incorrect anyways.
447+
def func(): ...
448+
449+
msg = (
450+
'getargs is deprecated since Python3.13, '
451+
'use Signature.from_code instead'
452+
)
453+
with self.assertWarnsRegex(DeprecationWarning, msg):
454+
inspect.getargs(func.__code__)
455+
456+
438457
class TestInterpreterStack(IsTestBase):
439458
def __init__(self, *args, **kwargs):
440459
unittest.TestCase.__init__(self, *args, **kwargs)
@@ -3674,6 +3693,41 @@ def test_signature_on_noncallable_mocks(self):
36743693
with self.assertRaises(TypeError):
36753694
inspect.signature(mock)
36763695

3696+
def test_signature_on_code_objects(self):
3697+
def func1(
3698+
a, b: int,
3699+
/,
3700+
c, d: str = 'a',
3701+
*args: int,
3702+
e, f: bool = True,
3703+
**kwargs: str,
3704+
) -> float:
3705+
...
3706+
3707+
def func2(a: str, b: int = 0, /): ...
3708+
def func3(): ...
3709+
def func4(*a, **k): ...
3710+
def func5(*, kw=False): ...
3711+
3712+
known_sigs = {
3713+
func1: '(a, b, /, c, d, *args, e, f, **kwargs)',
3714+
func2: '(a, b, /)',
3715+
func3: '()',
3716+
func4: '(*a, **k)',
3717+
func5: '(*, kw)',
3718+
}
3719+
3720+
for test_func, expected_sig in known_sigs.items():
3721+
with self.subTest(test_func=test_func, expected_sig=expected_sig):
3722+
self.assertEqual(
3723+
str(inspect.Signature.from_code(test_func.__code__)),
3724+
expected_sig,
3725+
)
3726+
3727+
def test_signature_on_non_code_objects(self):
3728+
with self.assertRaisesRegex(TypeError, '1 is not a code object'):
3729+
inspect.Signature.from_code(1)
3730+
36773731
def test_signature_equality(self):
36783732
def foo(a, *, b:int) -> float: pass
36793733
self.assertFalse(inspect.signature(foo) == 42)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Deprecate ``inspect.getargs``, use :meth:`inspect.Signature.from_code`
2+
instead.

0 commit comments

Comments
 (0)