diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index 959fde1b40e..83359930ede 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -417,6 +417,22 @@ by the local file. .. versionadded:: 3.2 +.. pdbcommand:: display [expression] + + Display the value of the expression if it changed, each time execution stops + in the current frame. + + Without expression, list all display expressions for the current frame. + + .. versionadded:: 3.2 + +.. pdbcommand:: undisplay [expression] + + Do not display the expression any more in the current frame. Without + expression, clear all display expressions for the current frame. + + .. versionadded:: 3.2 + .. pdbcommand:: interact Start an interative interpreter (using the :mod:`code` module) whose global diff --git a/Lib/pdb.py b/Lib/pdb.py index ac53eba135c..6776a3f4cf6 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -125,6 +125,12 @@ def lasti2lineno(code, lasti): return 0 +class _rstr(str): + """String that doesn't quote its repr.""" + def __repr__(self): + return self + + # Interaction prompt line will separate file and call info from code # text using value of line_prefix string. A newline and arrow may # be to your liking. You can set it once pdb is imported using the @@ -142,6 +148,7 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, self.use_rawinput = 0 self.prompt = '(Pdb) ' self.aliases = {} + self.displaying = {} self.mainpyfile = '' self._wait_for_mainpyfile = False self.tb_lineno = {} @@ -311,6 +318,20 @@ def _cmdloop(self): except KeyboardInterrupt: self.message('--KeyboardInterrupt--') + # Called before loop, handles display expressions + def preloop(self): + displaying = self.displaying.get(self.curframe) + if displaying: + for expr, oldvalue in displaying.items(): + newvalue = self._getval_except(expr) + # check for identity first; this prevents custom __eq__ to + # be called at every loop, and also prevents instances whose + # fields are changed to be displayed + if newvalue is not oldvalue and newvalue != oldvalue: + displaying[expr] = newvalue + self.message('display %s: %r [old: %r]' % + (expr, newvalue, oldvalue)) + def interaction(self, frame, traceback): if self.setup(frame, traceback): # no interaction desired at this time (happens if .pdbrc contains @@ -1041,6 +1062,17 @@ def _getval(self, arg): self.error(traceback.format_exception_only(*exc_info)[-1].strip()) raise + def _getval_except(self, arg, frame=None): + try: + if frame is None: + return eval(arg, self.curframe.f_globals, self.curframe_locals) + else: + return eval(arg, frame.f_globals, frame.f_locals) + except: + exc_info = sys.exc_info()[:2] + err = traceback.format_exception_only(*exc_info)[-1].strip() + return _rstr('** raised %s **' % err) + def do_p(self, arg): """p(rint) expression Print the value of the expression. @@ -1195,6 +1227,38 @@ def do_whatis(self, arg): # None of the above... self.message(type(value)) + def do_display(self, arg): + """display [expression] + + Display the value of the expression if it changed, each time execution + stops in the current frame. + + Without expression, list all display expressions for the current frame. + """ + if not arg: + self.message('Currently displaying:') + for item in self.displaying.get(self.curframe, {}).items(): + self.message('%s: %r' % item) + else: + val = self._getval_except(arg) + self.displaying.setdefault(self.curframe, {})[arg] = val + self.message('display %s: %r' % (arg, val)) + + def do_undisplay(self, arg): + """undisplay [expression] + + Do not display the expression any more in the current frame. + + Without expression, clear all display expressions for the current frame. + """ + if arg: + try: + del self.displaying.get(self.curframe, {})[arg] + except KeyError: + self.error('not displaying %s' % arg) + else: + self.displaying.pop(self.curframe, None) + def do_interact(self, arg): """interact @@ -1380,8 +1444,8 @@ def _runscript(self, filename): 'help', 'where', 'down', 'up', 'break', 'tbreak', 'clear', 'disable', 'enable', 'ignore', 'condition', 'commands', 'step', 'next', 'until', 'jump', 'return', 'retval', 'run', 'continue', 'list', 'longlist', - 'args', 'print', 'pp', 'whatis', 'source', 'interact', 'alias', - 'unalias', 'debug', 'quit', + 'args', 'print', 'pp', 'whatis', 'source', 'display', 'undisplay', + 'interact', 'alias', 'unalias', 'debug', 'quit', ] for _command in _help_order: diff --git a/Misc/NEWS b/Misc/NEWS index a741d6e5880..0d1c1d513a9 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -49,6 +49,8 @@ Core and Builtins Library ------- +- Add the "display" and "undisplay" pdb commands. + - Issue #7245: Add a SIGINT handler in pdb that allows to break a program again after a "continue" command.