From 75b3fe6c598fca96d7c7a23385939acf06c1b34e Mon Sep 17 00:00:00 2001 From: furkanonder Date: Wed, 25 Jan 2023 17:18:42 +0300 Subject: [PATCH 1/6] Fix inspect.getargvalues fails if arg name is not bound to a value --- Lib/inspect.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index 8bb3a375735af6a..d29910942f4077b 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1462,6 +1462,14 @@ def _formatannotation(annotation): return formatannotation(annotation, module) return _formatannotation +# A placeholder with as repr (using a string would have '' +# with quotes). This is used in formatargvalues function. +class _Deleted: + def __repr__(self): + return '' + +_deleted = _Deleted() +del _Deleted def formatargvalues(args, varargs, varkw, locals, formatarg=str, @@ -1476,7 +1484,7 @@ def formatargvalues(args, varargs, varkw, locals, argument is an optional function to format the sequence of arguments.""" def convert(name, locals=locals, formatarg=formatarg, formatvalue=formatvalue): - return formatarg(name) + formatvalue(locals[name]) + return formatarg(name) + formatvalue(locals.get(name, _deleted)) specs = [] for i in range(len(args)): specs.append(convert(args[i])) From 6c59a29275dbc82d55bd5c9c3be814fe249ca828 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 26 Jan 2023 09:36:27 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst diff --git a/Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst b/Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst new file mode 100644 index 000000000000000..c8c1dd3947810e1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst @@ -0,0 +1 @@ +Fix inspect.getargvalues function failing if arg name is not bound to a value. From 0afa69ae8a69f185b907414924bb94f4334dc366 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Fri, 31 Mar 2023 00:54:38 +0300 Subject: [PATCH 3/6] Add testcases for function arguments --- Lib/test/test_inspect.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 92aba519d28a085..cd1a20cbe475b2d 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -477,6 +477,20 @@ def test_frame(self): self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals), '(x=11, y=14)') + def return_current_frame(x): + return inspect.currentframe() + + self.assertEqual(inspect.formatargvalues(*inspect.getargvalues(return_current_frame(5))), "(x=5)") + self.assertEqual(inspect.formatargvalues(*inspect.getargvalues(return_current_frame(9))), "(x=9)") + + # See issue-61448 + def delete_arg(x): + del x + return inspect.currentframe() + + self.assertEqual(inspect.formatargvalues(*inspect.getargvalues(delete_arg(10))), "(x=)") + self.assertEqual(inspect.formatargvalues(*inspect.getargvalues(delete_arg(4))), "(x=)") + def test_previous_frame(self): args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back) self.assertEqual(args, ['a', 'b', 'c', 'd', 'e', 'f']) From 35a9cecf43365452a83981b48ccf9e8caad3d74e Mon Sep 17 00:00:00 2001 From: furkanonder Date: Fri, 31 Mar 2023 01:02:08 +0300 Subject: [PATCH 4/6] Add testcase for formatargvalues function --- Lib/test/test_inspect.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index cd1a20cbe475b2d..40c72cc7b2166fb 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -483,13 +483,14 @@ def return_current_frame(x): self.assertEqual(inspect.formatargvalues(*inspect.getargvalues(return_current_frame(5))), "(x=5)") self.assertEqual(inspect.formatargvalues(*inspect.getargvalues(return_current_frame(9))), "(x=9)") - # See issue-61448 - def delete_arg(x): - del x + # See issue-61448 + def test_formatargvalues_deleted_args(self): + def fun(a, b, x, y): + del b, y return inspect.currentframe() - self.assertEqual(inspect.formatargvalues(*inspect.getargvalues(delete_arg(10))), "(x=)") - self.assertEqual(inspect.formatargvalues(*inspect.getargvalues(delete_arg(4))), "(x=)") + args = inspect.getargvalues(fun(2, 4, 8, 16)) + self.assertEqual(inspect.formatargvalues(*args), '(a=2, b=, x=8, y=)') def test_previous_frame(self): args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back) From dd8a14ada9d7cebb77bab7cba1e7ca35930e08f7 Mon Sep 17 00:00:00 2001 From: Furkan Onder Date: Fri, 31 Mar 2023 01:05:48 +0300 Subject: [PATCH 5/6] Mention the co-author --- .../next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst b/Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst index c8c1dd3947810e1..2c64d08b0e4b09c 100644 --- a/Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst +++ b/Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst @@ -1 +1,2 @@ Fix inspect.getargvalues function failing if arg name is not bound to a value. +Co-authored-by Ezio Melotti. From c665b1c41e46d26235daed7b18f0f4fdadf6116b Mon Sep 17 00:00:00 2001 From: Lukasz Langa Date: Wed, 21 Oct 2026 14:31:49 -0700 Subject: [PATCH 6/6] gh-61448: Handle all deleted arguments without changing mapping lookups Cover deleted positional, keyword-only, and variadic arguments, preserve dictionary subclass lookup behavior, and clarify the documentation and NEWS entry. Co-authored-by: Muse Code (Meta Muse Spark) --- Doc/library/inspect.rst | 5 ++ Lib/inspect.py | 11 ++- Lib/test/test_inspect/test_inspect.py | 75 ++++++++++++++++++- ...3-01-26-09-36-24.gh-issue-61448.SOAkvt.rst | 5 +- 4 files changed, 88 insertions(+), 8 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 196479eb7550e25..29bdae28b1fe268 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -1347,6 +1347,11 @@ Classes and functions :func:`getargvalues`. The format\* arguments are the corresponding optional formatting functions that are called to turn names and values into strings. + Arguments whose names are absent from *locals*, including ``*`` and ``**`` + arguments, are displayed with a value of ````. A custom *formatvalue* + function receives a placeholder object whose :func:`repr` is ```` + for these arguments. + .. note:: This function was inadvertently marked as deprecated in Python 3.5. diff --git a/Lib/inspect.py b/Lib/inspect.py index f8176aeb24263a8..d06c8078ded8702 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1419,14 +1419,19 @@ def formatargvalues(args, varargs, varkw, locals, argument is an optional function to format the sequence of arguments.""" def convert(name, locals=locals, formatarg=formatarg, formatvalue=formatvalue): - return formatarg(name) + formatvalue(locals.get(name, _deleted)) + formatted_name = formatarg(name) + try: + value = locals[name] + except KeyError: + value = _deleted + return formatted_name + formatvalue(value) specs = [] for i in range(len(args)): specs.append(convert(args[i])) if varargs: - specs.append(formatvarargs(varargs) + formatvalue(locals[varargs])) + specs.append(convert(varargs, formatarg=formatvarargs)) if varkw: - specs.append(formatvarkw(varkw) + formatvalue(locals[varkw])) + specs.append(convert(varkw, formatarg=formatvarkw)) return '(' + ', '.join(specs) + ')' def _missing_arguments(f_name, argnames, pos, values): diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 6df5c072fb0e1ba..c5581041f044b17 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -598,8 +598,10 @@ def test_frame(self): def return_current_frame(x): return inspect.currentframe() - self.assertEqual(inspect.formatargvalues(*inspect.getargvalues(return_current_frame(5))), "(x=5)") - self.assertEqual(inspect.formatargvalues(*inspect.getargvalues(return_current_frame(9))), "(x=9)") + self.assertEqual(inspect.formatargvalues( + *inspect.getargvalues(return_current_frame(5))), '(x=5)') + self.assertEqual(inspect.formatargvalues( + *inspect.getargvalues(return_current_frame(9))), '(x=9)') # See issue-61448 def test_formatargvalues_deleted_args(self): @@ -608,7 +610,74 @@ def fun(a, b, x, y): return inspect.currentframe() args = inspect.getargvalues(fun(2, 4, 8, 16)) - self.assertEqual(inspect.formatargvalues(*args), '(a=2, b=, x=8, y=)') + self.assertEqual(inspect.formatargvalues(*args), + '(a=2, b=, x=8, y=)') + + def test_formatargvalues_deleted_argument_kinds(self): + cases = ( + ((), "(x=None, a=2, k=4, *args=(3,), **kwargs={'z': 5})"), + (('x',), + "(x=, a=2, k=4, *args=(3,), **kwargs={'z': 5})"), + (('a',), + "(x=None, a=, k=4, *args=(3,), **kwargs={'z': 5})"), + (('k',), + "(x=None, a=2, k=, *args=(3,), **kwargs={'z': 5})"), + (('args',), + "(x=None, a=2, k=4, *args=, **kwargs={'z': 5})"), + (('kwargs',), + '(x=None, a=2, k=4, *args=(3,), **kwargs=)'), + (('x', 'a', 'k', 'args', 'kwargs'), + '(x=, a=, k=, ' + '*args=, **kwargs=)'), + ) + for deleted, expected in cases: + with self.subTest(deleted=deleted): + def fun(x, /, a, *args, k, **kwargs): + if 'x' in deleted: + del x + if 'a' in deleted: + del a + if 'k' in deleted: + del k + if 'args' in deleted: + del args + if 'kwargs' in deleted: + del kwargs + return inspect.currentframe() + + frame = fun(None, 2, 3, k=4, z=5) + try: + self.assertEqual(inspect.formatargvalues( + *inspect.getargvalues(frame)), expected) + finally: + frame.clear() + + def test_formatargvalues_mapping_lookup(self): + class LookupDict(dict): + def __getitem__(self, name): + return super().__getitem__(name) + 1 + + for args, varargs, varkw, name in ( + (['x'], None, None, 'x'), + ([], 'args', None, '*args'), + ([], None, 'kwargs', '**kwargs'), + ): + with self.subTest(name=name): + values = collections.defaultdict(lambda: 42) + self.assertEqual(inspect.formatargvalues( + args, varargs, varkw, values), f'({name}=42)') + values = LookupDict({name.lstrip('*'): 5}) + self.assertEqual(inspect.formatargvalues( + args, varargs, varkw, values), f'({name}=6)') + + def test_formatargvalues_deleted_custom_formatters(self): + self.assertEqual(inspect.formatargvalues( + ['x'], 'args', 'kwargs', {}, + formatarg=lambda name: name.upper(), + formatvarargs=lambda name: '*' + name.upper(), + formatvarkw=lambda name: '**' + name.upper(), + formatvalue=lambda value: ':' + repr(value)), + '(X:, *ARGS:, **KWARGS:)') def test_previous_frame(self): args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back) diff --git a/Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst b/Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst index 2c64d08b0e4b09c..6ea99ba3310b7cd 100644 --- a/Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst +++ b/Misc/NEWS.d/next/Library/2023-01-26-09-36-24.gh-issue-61448.SOAkvt.rst @@ -1,2 +1,3 @@ -Fix inspect.getargvalues function failing if arg name is not bound to a value. -Co-authored-by Ezio Melotti. +Fix :func:`inspect.formatargvalues` raising :exc:`KeyError` for deleted +arguments, including ``*args`` and ``**kwargs``, by displaying ````. +Patch by Ezio Melotti and Furkan Onder.