mirror of https://github.com/python/cpython
gh-103693: Add convenience variable feature to `pdb` (#103694)
This commit is contained in:
parent
524a7f77fd
commit
0fc58c66ba
|
@ -263,6 +263,21 @@ 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 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
|
||||
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
|
||||
* ``$_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
|
||||
|
|
|
@ -408,6 +408,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
|
||||
------
|
||||
|
||||
|
|
17
Lib/pdb.py
17
Lib/pdb.py
|
@ -270,6 +270,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||
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,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||
# 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(self.curframe, '_frame', self.curframe)
|
||||
return self.execRcLines()
|
||||
|
||||
# Can be executed earlier than 'setup' if desired
|
||||
|
@ -359,6 +362,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||
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)
|
||||
|
||||
|
@ -369,6 +373,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||
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
|
||||
|
@ -394,6 +399,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||
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 +483,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||
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 +536,13 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||
def error(self, msg):
|
||||
print('***', msg, file=self.stdout)
|
||||
|
||||
# convenience variables
|
||||
|
||||
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.
|
||||
|
||||
|
@ -1018,6 +1034,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||
self.curindex = number
|
||||
self.curframe = self.stack[self.curindex][0]
|
||||
self.curframe_locals = self.curframe.f_locals
|
||||
self.set_convenience_variable(self.curframe, '_frame', self.curframe)
|
||||
self.print_stack_entry(self.stack[self.curindex])
|
||||
self.lineno = None
|
||||
|
||||
|
|
|
@ -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', # Check exception convenience variable
|
||||
... 'next',
|
||||
... '$_exception', # Exception should be gone
|
||||
... 'return',
|
||||
... '$_retval', # Check return convenience variable
|
||||
... 'continue',
|
||||
... ]):
|
||||
... test_function()
|
||||
> <doctest test.test_pdb.test_convenience_variables[0]>(3)util_function()
|
||||
-> try:
|
||||
(Pdb) $_frame.f_lineno
|
||||
3
|
||||
(Pdb) $a = 10
|
||||
(Pdb) $a
|
||||
10
|
||||
(Pdb) p $a + 2
|
||||
12
|
||||
(Pdb) u
|
||||
> <doctest test.test_pdb.test_convenience_variables[1]>(2)test_function()
|
||||
-> util_function()
|
||||
(Pdb) $_frame.f_lineno
|
||||
2
|
||||
(Pdb) $a
|
||||
10
|
||||
(Pdb) d
|
||||
> <doctest test.test_pdb.test_convenience_variables[0]>(3)util_function()
|
||||
-> try:
|
||||
(Pdb) next
|
||||
> <doctest test.test_pdb.test_convenience_variables[0]>(4)util_function()
|
||||
-> raise Exception('test')
|
||||
(Pdb) $a
|
||||
*** KeyError: 'a'
|
||||
(Pdb) next
|
||||
Exception: test
|
||||
> <doctest test.test_pdb.test_convenience_variables[0]>(4)util_function()
|
||||
-> raise Exception('test')
|
||||
(Pdb) $_exception
|
||||
Exception('test')
|
||||
(Pdb) next
|
||||
> <doctest test.test_pdb.test_convenience_variables[0]>(5)util_function()
|
||||
-> except:
|
||||
(Pdb) $_exception
|
||||
*** KeyError: '_exception'
|
||||
(Pdb) return
|
||||
--Return--
|
||||
> <doctest test.test_pdb.test_convenience_variables[0]>(7)util_function()->1
|
||||
-> return 1
|
||||
(Pdb) $_retval
|
||||
1
|
||||
(Pdb) continue
|
||||
"""
|
||||
|
||||
def test_post_mortem():
|
||||
"""Test post mortem traceback debugging.
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Add convenience variable feature to :mod:`pdb`
|
Loading…
Reference in New Issue