Skip to content

Commit c72b435

Browse files
furkanonderambv
andauthored
gh-61448: Fix inspect.getargvalues fails if arg name is not bound to a value (#101318)
Co-authored-by: Muse Code (Meta Muse Spark) Co-authored-by: Lukasz Langa <ambv@meta.com>
1 parent 1c5b959 commit c72b435

4 files changed

Lines changed: 108 additions & 3 deletions

File tree

‎Doc/library/inspect.rst‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,11 @@ Classes and functions
13471347
:func:`getargvalues`. The format\* arguments are the corresponding optional
13481348
formatting functions that are called to turn names and values into strings.
13491349

1350+
Arguments whose names are absent from *locals*, including ``*`` and ``**``
1351+
arguments, are displayed with a value of ``<deleted>``. A custom *formatvalue*
1352+
function receives a placeholder object whose :func:`repr` is ``<deleted>``
1353+
for these arguments.
1354+
13501355
.. note::
13511356
This function was inadvertently marked as deprecated in Python 3.5.
13521357

‎Lib/inspect.py‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,6 +1397,14 @@ def _formatannotation(annotation):
13971397
return formatannotation(annotation, module)
13981398
return _formatannotation
13991399

1400+
# A placeholder with <deleted> as repr (using a string would have '<deleted>'
1401+
# with quotes). This is used in formatargvalues function.
1402+
class _Deleted:
1403+
def __repr__(self):
1404+
return '<deleted>'
1405+
1406+
_deleted = _Deleted()
1407+
del _Deleted
14001408

14011409
def formatargvalues(args, varargs, varkw, locals,
14021410
formatarg=str,
@@ -1411,14 +1419,19 @@ def formatargvalues(args, varargs, varkw, locals,
14111419
argument is an optional function to format the sequence of arguments."""
14121420
def convert(name, locals=locals,
14131421
formatarg=formatarg, formatvalue=formatvalue):
1414-
return formatarg(name) + formatvalue(locals[name])
1422+
formatted_name = formatarg(name)
1423+
try:
1424+
value = locals[name]
1425+
except KeyError:
1426+
value = _deleted
1427+
return formatted_name + formatvalue(value)
14151428
specs = []
14161429
for i in range(len(args)):
14171430
specs.append(convert(args[i]))
14181431
if varargs:
1419-
specs.append(formatvarargs(varargs) + formatvalue(locals[varargs]))
1432+
specs.append(convert(varargs, formatarg=formatvarargs))
14201433
if varkw:
1421-
specs.append(formatvarkw(varkw) + formatvalue(locals[varkw]))
1434+
specs.append(convert(varkw, formatarg=formatvarkw))
14221435
return '(' + ', '.join(specs) + ')'
14231436

14241437
def _missing_arguments(f_name, argnames, pos, values):

‎Lib/test/test_inspect/test_inspect.py‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,90 @@ def test_frame(self):
595595
self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals),
596596
'(x=11, y=14)')
597597

598+
def return_current_frame(x):
599+
return inspect.currentframe()
600+
601+
self.assertEqual(inspect.formatargvalues(
602+
*inspect.getargvalues(return_current_frame(5))), '(x=5)')
603+
self.assertEqual(inspect.formatargvalues(
604+
*inspect.getargvalues(return_current_frame(9))), '(x=9)')
605+
606+
# See issue-61448
607+
def test_formatargvalues_deleted_args(self):
608+
def fun(a, b, x, y):
609+
del b, y
610+
return inspect.currentframe()
611+
612+
args = inspect.getargvalues(fun(2, 4, 8, 16))
613+
self.assertEqual(inspect.formatargvalues(*args),
614+
'(a=2, b=<deleted>, x=8, y=<deleted>)')
615+
616+
def test_formatargvalues_deleted_argument_kinds(self):
617+
cases = (
618+
((), "(x=None, a=2, k=4, *args=(3,), **kwargs={'z': 5})"),
619+
(('x',),
620+
"(x=<deleted>, a=2, k=4, *args=(3,), **kwargs={'z': 5})"),
621+
(('a',),
622+
"(x=None, a=<deleted>, k=4, *args=(3,), **kwargs={'z': 5})"),
623+
(('k',),
624+
"(x=None, a=2, k=<deleted>, *args=(3,), **kwargs={'z': 5})"),
625+
(('args',),
626+
"(x=None, a=2, k=4, *args=<deleted>, **kwargs={'z': 5})"),
627+
(('kwargs',),
628+
'(x=None, a=2, k=4, *args=(3,), **kwargs=<deleted>)'),
629+
(('x', 'a', 'k', 'args', 'kwargs'),
630+
'(x=<deleted>, a=<deleted>, k=<deleted>, '
631+
'*args=<deleted>, **kwargs=<deleted>)'),
632+
)
633+
for deleted, expected in cases:
634+
with self.subTest(deleted=deleted):
635+
def fun(x, /, a, *args, k, **kwargs):
636+
if 'x' in deleted:
637+
del x
638+
if 'a' in deleted:
639+
del a
640+
if 'k' in deleted:
641+
del k
642+
if 'args' in deleted:
643+
del args
644+
if 'kwargs' in deleted:
645+
del kwargs
646+
return inspect.currentframe()
647+
648+
frame = fun(None, 2, 3, k=4, z=5)
649+
try:
650+
self.assertEqual(inspect.formatargvalues(
651+
*inspect.getargvalues(frame)), expected)
652+
finally:
653+
frame.clear()
654+
655+
def test_formatargvalues_mapping_lookup(self):
656+
class LookupDict(dict):
657+
def __getitem__(self, name):
658+
return super().__getitem__(name) + 1
659+
660+
for args, varargs, varkw, name in (
661+
(['x'], None, None, 'x'),
662+
([], 'args', None, '*args'),
663+
([], None, 'kwargs', '**kwargs'),
664+
):
665+
with self.subTest(name=name):
666+
values = collections.defaultdict(lambda: 42)
667+
self.assertEqual(inspect.formatargvalues(
668+
args, varargs, varkw, values), f'({name}=42)')
669+
values = LookupDict({name.lstrip('*'): 5})
670+
self.assertEqual(inspect.formatargvalues(
671+
args, varargs, varkw, values), f'({name}=6)')
672+
673+
def test_formatargvalues_deleted_custom_formatters(self):
674+
self.assertEqual(inspect.formatargvalues(
675+
['x'], 'args', 'kwargs', {},
676+
formatarg=lambda name: name.upper(),
677+
formatvarargs=lambda name: '*' + name.upper(),
678+
formatvarkw=lambda name: '**' + name.upper(),
679+
formatvalue=lambda value: ':' + repr(value)),
680+
'(X:<deleted>, *ARGS:<deleted>, **KWARGS:<deleted>)')
681+
598682
def test_previous_frame(self):
599683
args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back)
600684
self.assertEqual(args, ['a', 'b', 'c', 'd', 'e', 'f'])
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`inspect.formatargvalues` raising :exc:`KeyError` for deleted
2+
arguments, including ``*args`` and ``**kwargs``, by displaying ``<deleted>``.
3+
Patch by Ezio Melotti and Furkan Onder.

0 commit comments

Comments
 (0)