From 7db9d37f9e2916af7b9b9954529da83cedc93eac Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sat, 22 Apr 2023 14:03:46 -0700 Subject: [PATCH 1/7] Add convenience variable feature --- Doc/library/pdb.rst | 13 ++++++++ Lib/pdb.py | 21 ++++++++++++ Lib/test/test_pdb.py | 78 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index 5bc48a6d5f77fd..64390ecb249dad 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -263,6 +263,19 @@ the commands; the input is split at the first ``;;`` pair, even if it is in the middle of a quoted string. A workaround for strings with double semicolons is to use implicit string concatenation ``';'';'`` or ``";"";"``. +To set temporary global variables, use *convenience variable*. A *convenience +variable* is a variable whose name starts with ``$``. For example, ``$foo = 1`` +set a global variable ``$foo`` which you can use in the debugger session. The +*convenience variables* are cleared when the program resumes execution so it's +less likely to interfere with your program compared to using normal variables +like ``foo = 1``. + +There are three preset *convenience variables*: + +* ``$_frame``: the current frame you are debugging +* ``$_return``: the return value if the frame is returning +* ``$_exception``: the ``(exc_type, exc_value)`` tuple if the frame is raising an exception + .. index:: pair: .pdbrc; file triple: debugger; configuration; file diff --git a/Lib/pdb.py b/Lib/pdb.py index a3553b345a8dd3..9ba9856b128994 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -270,6 +270,8 @@ def forget(self): self.lineno = None self.stack = [] self.curindex = 0 + if hasattr(self, 'curframe') and self.curframe: + self.curframe.f_globals.pop('__pdb_convenience_variables', None) self.curframe = None self.tb_lineno.clear() @@ -288,6 +290,11 @@ def setup(self, f, tb): # locals whenever the .f_locals accessor is called, so we # cache it here to ensure that modifications are not overwritten. self.curframe_locals = self.curframe.f_locals + self.set_convenience_variable('_frame', self.curframe) + if '__return__' in self.curframe_locals: + self.set_convenience_variable('_return', self.curframe_locals['__return__']) + if '__exception__' in self.curframe_locals: + self.set_convenience_variable('_exception', self.curframe_locals['__exception__']) return self.execRcLines() # Can be executed earlier than 'setup' if desired @@ -394,6 +401,7 @@ def _cmdloop(self): self.message('--KeyboardInterrupt--') # Called before loop, handles display expressions + # Set up convenience variable containers def preloop(self): displaying = self.displaying.get(self.curframe) if displaying: @@ -477,6 +485,9 @@ def precmd(self, line): next = line[marker+2:].lstrip() self.cmdqueue.append(next) line = line[:marker].rstrip() + + # Replace all the convenience variables + line = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'__pdb_convenience_variables["\1"]', line) return line def onecmd(self, line): @@ -527,6 +538,15 @@ def message(self, msg): def error(self, msg): print('***', msg, file=self.stdout) + # convenience variables + + def set_convenience_variable(self, name, value): + if not hasattr(self, 'curframe') or not self.curframe: + return + if '__pdb_convenience_variables' not in self.curframe.f_globals: + self.curframe.f_globals['__pdb_convenience_variables'] = {} + self.curframe.f_globals['__pdb_convenience_variables'][name] = value + # Generic completion functions. Individual complete_foo methods can be # assigned below to one of these functions. @@ -1018,6 +1038,7 @@ def _select_frame(self, number): self.curindex = number self.curframe = self.stack[self.curindex][0] self.curframe_locals = self.curframe.f_locals + self.set_convenience_variable('_frame', self.curframe) self.print_stack_entry(self.stack[self.curindex]) self.lineno = None diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 94b441720f258c..9a6b15f6b441cf 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -746,6 +746,84 @@ def test_pdb_where_command(): (Pdb) continue """ +def test_convenience_variables(): + """Test convenience variables + + >>> def util_function(): + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + ... try: + ... raise Exception('test') + ... except: + ... pass + ... return 1 + + >>> def test_function(): + ... util_function() + + >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + ... '$_frame.f_lineno', # Check frame convenience variable + ... '$a = 10', # Set a convenience variable + ... '$a', # Print its value + ... 'p $a + 2', # Do some calculation + ... 'u', # Switch frame + ... '$_frame.f_lineno', # Make sure the frame changed + ... '$a', # Make sure the value persists + ... 'd', # Go back to the original frame + ... 'next', + ... '$a', # The value should be gone + ... 'next', + ... '$_exception[1]', # Check exception convenience variable + ... 'next', + ... '$_exception', # Exception should be gone + ... 'return', + ... '$_return', # Check return convenience variable + ... 'continue', + ... ]): + ... test_function() + > (3)util_function() + -> try: + (Pdb) $_frame.f_lineno + 3 + (Pdb) $a = 10 + (Pdb) $a + 10 + (Pdb) p $a + 2 + 12 + (Pdb) u + > (2)test_function() + -> util_function() + (Pdb) $_frame.f_lineno + 2 + (Pdb) $a + 10 + (Pdb) d + > (3)util_function() + -> try: + (Pdb) next + > (4)util_function() + -> raise Exception('test') + (Pdb) $a + *** KeyError: 'a' + (Pdb) next + Exception: test + > (4)util_function() + -> raise Exception('test') + (Pdb) $_exception[1] + Exception('test') + (Pdb) next + > (5)util_function() + -> except: + (Pdb) $_exception + (, Exception('test')) + (Pdb) return + --Return-- + > (7)util_function()->1 + -> return 1 + (Pdb) $_return + 1 + (Pdb) continue + """ + def test_post_mortem(): """Test post mortem traceback debugging. From f7a11af86fde3ac51aa6933e9891021177b5ff2f Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sat, 22 Apr 2023 14:29:08 -0700 Subject: [PATCH 2/7] Change _return to _retval --- Doc/library/pdb.rst | 2 +- Lib/pdb.py | 2 +- Lib/test/test_pdb.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index 64390ecb249dad..f6bbe033ffce04 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -273,7 +273,7 @@ like ``foo = 1``. There are three preset *convenience variables*: * ``$_frame``: the current frame you are debugging -* ``$_return``: the return value if the frame is returning +* ``$_retval``: the return value if the frame is returning * ``$_exception``: the ``(exc_type, exc_value)`` tuple if the frame is raising an exception .. index:: diff --git a/Lib/pdb.py b/Lib/pdb.py index 9ba9856b128994..207ef8a3adf590 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -292,7 +292,7 @@ def setup(self, f, tb): self.curframe_locals = self.curframe.f_locals self.set_convenience_variable('_frame', self.curframe) if '__return__' in self.curframe_locals: - self.set_convenience_variable('_return', self.curframe_locals['__return__']) + self.set_convenience_variable('_retval', self.curframe_locals['__return__']) if '__exception__' in self.curframe_locals: self.set_convenience_variable('_exception', self.curframe_locals['__exception__']) return self.execRcLines() diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 9a6b15f6b441cf..a94d157d1ddaa1 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -776,7 +776,7 @@ def test_convenience_variables(): ... 'next', ... '$_exception', # Exception should be gone ... 'return', - ... '$_return', # Check return convenience variable + ... '$_retval', # Check return convenience variable ... 'continue', ... ]): ... test_function() @@ -819,7 +819,7 @@ def test_convenience_variables(): --Return-- > (7)util_function()->1 -> return 1 - (Pdb) $_return + (Pdb) $_retval 1 (Pdb) continue """ From a202abf9051efddccb3d4ee41f925b3e35a9a22c Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 22 Apr 2023 21:34:14 +0000 Subject: [PATCH 3/7] =?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-04-22-21-34-13.gh-issue-103693.SBtuLQ.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2023-04-22-21-34-13.gh-issue-103693.SBtuLQ.rst diff --git a/Misc/NEWS.d/next/Library/2023-04-22-21-34-13.gh-issue-103693.SBtuLQ.rst b/Misc/NEWS.d/next/Library/2023-04-22-21-34-13.gh-issue-103693.SBtuLQ.rst new file mode 100644 index 00000000000000..52c68bfc9ceea4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-22-21-34-13.gh-issue-103693.SBtuLQ.rst @@ -0,0 +1 @@ +Add convenience variable feature to :mod:`pdb` From 14152102b15d86ab80b2380356254bb65349f9ac Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sun, 23 Apr 2023 12:13:10 -0700 Subject: [PATCH 4/7] Use exception value instead of tuple Make _exception non-persistent --- Doc/library/pdb.rst | 2 +- Lib/pdb.py | 20 ++++++++------------ Lib/test/test_pdb.py | 6 +++--- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index f6bbe033ffce04..4c803cec6a9fb1 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -274,7 +274,7 @@ There are three preset *convenience variables*: * ``$_frame``: the current frame you are debugging * ``$_retval``: the return value if the frame is returning -* ``$_exception``: the ``(exc_type, exc_value)`` tuple if the frame is raising an exception +* ``$_exception``: the exception if the frame is raising an exception .. index:: pair: .pdbrc; file diff --git a/Lib/pdb.py b/Lib/pdb.py index 207ef8a3adf590..e7d885ce5179ed 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -290,11 +290,7 @@ def setup(self, f, tb): # locals whenever the .f_locals accessor is called, so we # cache it here to ensure that modifications are not overwritten. self.curframe_locals = self.curframe.f_locals - self.set_convenience_variable('_frame', self.curframe) - if '__return__' in self.curframe_locals: - self.set_convenience_variable('_retval', self.curframe_locals['__return__']) - if '__exception__' in self.curframe_locals: - self.set_convenience_variable('_exception', self.curframe_locals['__exception__']) + self.set_convenience_variable(self.curframe, '_frame', self.curframe) return self.execRcLines() # Can be executed earlier than 'setup' if desired @@ -366,6 +362,7 @@ def user_return(self, frame, return_value): if self._wait_for_mainpyfile: return frame.f_locals['__return__'] = return_value + self.set_convenience_variable(frame, '_retval', return_value) self.message('--Return--') self.interaction(frame, None) @@ -376,6 +373,7 @@ def user_exception(self, frame, exc_info): return exc_type, exc_value, exc_traceback = exc_info frame.f_locals['__exception__'] = exc_type, exc_value + self.set_convenience_variable(frame, '_exception', exc_value) # An 'Internal StopIteration' exception is an exception debug event # issued by the interpreter when handling a subgenerator run with @@ -540,12 +538,10 @@ def error(self, msg): # convenience variables - def set_convenience_variable(self, name, value): - if not hasattr(self, 'curframe') or not self.curframe: - return - if '__pdb_convenience_variables' not in self.curframe.f_globals: - self.curframe.f_globals['__pdb_convenience_variables'] = {} - self.curframe.f_globals['__pdb_convenience_variables'][name] = value + def set_convenience_variable(self, frame, name, value): + if '__pdb_convenience_variables' not in frame.f_globals: + frame.f_globals['__pdb_convenience_variables'] = {} + frame.f_globals['__pdb_convenience_variables'][name] = value # Generic completion functions. Individual complete_foo methods can be # assigned below to one of these functions. @@ -1038,7 +1034,7 @@ def _select_frame(self, number): self.curindex = number self.curframe = self.stack[self.curindex][0] self.curframe_locals = self.curframe.f_locals - self.set_convenience_variable('_frame', self.curframe) + self.set_convenience_variable(self.curframe, '_frame', self.curframe) self.print_stack_entry(self.stack[self.curindex]) self.lineno = None diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index a94d157d1ddaa1..8a171c3ecd8831 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -772,7 +772,7 @@ def test_convenience_variables(): ... 'next', ... '$a', # The value should be gone ... 'next', - ... '$_exception[1]', # Check exception convenience variable + ... '$_exception', # Check exception convenience variable ... 'next', ... '$_exception', # Exception should be gone ... 'return', @@ -808,13 +808,13 @@ def test_convenience_variables(): Exception: test > (4)util_function() -> raise Exception('test') - (Pdb) $_exception[1] + (Pdb) $_exception Exception('test') (Pdb) next > (5)util_function() -> except: (Pdb) $_exception - (, Exception('test')) + *** KeyError: '_exception' (Pdb) return --Return-- > (7)util_function()->1 From ac2314875245ec90cc2da62e8c660628c4bd0a94 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Mon, 1 May 2023 15:31:20 -0700 Subject: [PATCH 5/7] Update Doc/library/pdb.rst Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- Doc/library/pdb.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index 4c803cec6a9fb1..bdbd419a00efc4 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -265,7 +265,7 @@ is to use implicit string concatenation ``';'';'`` or ``";"";"``. To set temporary global variables, use *convenience variable*. A *convenience variable* is a variable whose name starts with ``$``. For example, ``$foo = 1`` -set a global variable ``$foo`` which you can use in the debugger session. The +sets a global variable ``$foo`` which you can use in the debugger session. The *convenience variables* are cleared when the program resumes execution so it's less likely to interfere with your program compared to using normal variables like ``foo = 1``. From 150250b9f95ba1a4676e3990595bbcb582d1049a Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Mon, 1 May 2023 15:45:02 -0700 Subject: [PATCH 6/7] Update docs and whatsnew --- Doc/library/pdb.rst | 4 +++- Doc/whatsnew/3.12.rst | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index bdbd419a00efc4..195e939772a5a1 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -263,7 +263,7 @@ the commands; the input is split at the first ``;;`` pair, even if it is in the middle of a quoted string. A workaround for strings with double semicolons is to use implicit string concatenation ``';'';'`` or ``";"";"``. -To set temporary global variables, use *convenience variable*. A *convenience +To set a temporary global variable, use *convenience variable*. A *convenience variable* is a variable whose name starts with ``$``. For example, ``$foo = 1`` sets a global variable ``$foo`` which you can use in the debugger session. The *convenience variables* are cleared when the program resumes execution so it's @@ -276,6 +276,8 @@ There are three preset *convenience variables*: * ``$_retval``: the return value if the frame is returning * ``$_exception``: the exception if the frame is raising an exception +.. versionadded:: 3.12 + .. index:: pair: .pdbrc; file triple: debugger; configuration; file diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index f9406653e625b5..3bfd8fcbf3a4f3 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -356,6 +356,14 @@ os.path * Add :func:`os.path.splitroot` to split a path into a triad ``(drive, root, tail)``. (Contributed by Barney Gale in :gh:`101000`.) +pdb +--- + +* Add convenience variables to hold values temporarily for debug session + and provide quick access to values like the current frame or the return + value. + (Contributed by Tian Gao in :gh:`103693`.) + shutil ------ From f626d36d6a1d6140b3d6cc9f6b1b14b765ade8f4 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Mon, 1 May 2023 16:04:53 -0700 Subject: [PATCH 7/7] Update Doc/library/pdb.rst Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- Doc/library/pdb.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index 195e939772a5a1..8a386aa77368f2 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -263,7 +263,7 @@ the commands; the input is split at the first ``;;`` pair, even if it is in the middle of a quoted string. A workaround for strings with double semicolons is to use implicit string concatenation ``';'';'`` or ``";"";"``. -To set a temporary global variable, use *convenience variable*. A *convenience +To set a temporary global variable, use a *convenience variable*. A *convenience variable* is a variable whose name starts with ``$``. For example, ``$foo = 1`` sets a global variable ``$foo`` which you can use in the debugger session. The *convenience variables* are cleared when the program resumes execution so it's