[Python-checkins] r83293 - in python/branches/py3k: Doc/library/pdb.rst Lib/pdb.py Lib/test/test_pdb.py Misc/NEWS

georg.brandl python-checkins at python.org
Fri Jul 30 20:46:38 CEST 2010


Author: georg.brandl
Date: Fri Jul 30 20:46:38 2010
New Revision: 83293

Log:
Show the traceback line numbers as well as the current line numbers if an exception is being debugged.  Courtesy of pdb++ by Antonio Cuni.  Also document -> and >> markers for "list".

Modified:
   python/branches/py3k/Doc/library/pdb.rst
   python/branches/py3k/Lib/pdb.py
   python/branches/py3k/Lib/test/test_pdb.py
   python/branches/py3k/Misc/NEWS

Modified: python/branches/py3k/Doc/library/pdb.rst
==============================================================================
--- python/branches/py3k/Doc/library/pdb.rst	(original)
+++ python/branches/py3k/Doc/library/pdb.rst	Fri Jul 30 20:46:38 2010
@@ -368,9 +368,18 @@
    list 11 lines around at that line.  With two arguments, list the given range;
    if the second argument is less than the first, it is interpreted as a count.
 
+   The current line in the current frame is indicated by ``->``.  If an
+   exception is being debugged, the line where the exception was originally
+   raised or propagated is indicated by ``>>``, if it differs from the current
+   line.
+
+   .. versionadded:: 3.2
+      The ``>>`` marker.
+
 .. pdbcommand:: ll | longlist
 
-   List all source code for the current function or frame.
+   List all source code for the current function or frame.  Interesting lines
+   are marked as for :pdbcmd:`list`.
 
    .. versionadded:: 3.2
 

Modified: python/branches/py3k/Lib/pdb.py
==============================================================================
--- python/branches/py3k/Lib/pdb.py	(original)
+++ python/branches/py3k/Lib/pdb.py	Fri Jul 30 20:46:38 2010
@@ -70,8 +70,10 @@
 import linecache
 import cmd
 import bdb
+import dis
 import os
 import re
+import code
 import pprint
 import traceback
 import inspect
@@ -107,14 +109,22 @@
 
 def getsourcelines(obj):
     lines, lineno = inspect.findsource(obj)
-    if inspect.isframe(obj) and lineno == 0 and \
-           obj.f_globals is obj.f_locals:
+    if inspect.isframe(obj) and obj.f_globals is obj.f_locals:
         # must be a module frame: do not try to cut a block out of it
-        return lines, 0
+        return lines, 1
     elif inspect.ismodule(obj):
-        return lines, 0
+        return lines, 1
     return inspect.getblock(lines[lineno:]), lineno+1
 
+def lasti2lineno(code, lasti):
+    linestarts = list(dis.findlinestarts(code))
+    linestarts.reverse()
+    for i, lineno in linestarts:
+        if lasti >= i:
+            return lineno
+    return 0
+
+
 # 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
@@ -133,6 +143,7 @@
         self.aliases = {}
         self.mainpyfile = ''
         self._wait_for_mainpyfile = 0
+        self.tb_lineno = {}
         # Try to load readline if it exists
         try:
             import readline
@@ -179,10 +190,18 @@
         self.stack = []
         self.curindex = 0
         self.curframe = None
+        self.tb_lineno.clear()
 
-    def setup(self, f, t):
+    def setup(self, f, tb):
         self.forget()
-        self.stack, self.curindex = self.get_stack(f, t)
+        self.stack, self.curindex = self.get_stack(f, tb)
+        while tb:
+            # when setting up post-mortem debugging with a traceback, save all
+            # the original line numbers to be displayed along the current line
+            # numbers (which can be different, e.g. due to finally clauses)
+            lineno = lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti)
+            self.tb_lineno[tb.tb_frame] = lineno
+            tb = tb.tb_next
         self.curframe = self.stack[self.curindex][0]
         # The f_locals dictionary is updated from the actual frame
         # locals whenever the .f_locals accessor is called, so we
@@ -1005,13 +1024,18 @@
 
     def do_list(self, arg):
         """l(ist) [first [,last] | .]
-        List source code for the current file.
-        Without arguments, list 11 lines around the current line
-        or continue the previous listing.
-        With . as argument, list 11 lines around the current line.
-        With one argument, list 11 lines starting at that line.
-        With two arguments, list the given range;
-        if the second argument is less than the first, it is a count.
+
+        List source code for the current file.  Without arguments,
+        list 11 lines around the current line or continue the previous
+        listing.  With . as argument, list 11 lines around the current
+        line.  With one argument, list 11 lines starting at that line.
+        With two arguments, list the given range; if the second
+        argument is less than the first, it is a count.
+
+        The current line in the current frame is indicated by "->".
+        If an exception is being debugged, the line where the
+        exception was originally raised or propagated is indicated by
+        ">>", if it differs from the current line.
         """
         self.lastcmd = 'list'
         last = None
@@ -1039,10 +1063,9 @@
         filename = self.curframe.f_code.co_filename
         breaklist = self.get_file_breaks(filename)
         try:
-            # XXX add tb_lineno feature
             lines = linecache.getlines(filename, self.curframe.f_globals)
             self._print_lines(lines[first-1:last], first, breaklist,
-                              self.curframe.f_lineno, -1)
+                              self.curframe)
             self.lineno = min(last, len(lines))
             if len(lines) < last:
                 self.message('[EOF]')
@@ -1061,7 +1084,7 @@
         except IOError as err:
             self.error(err)
             return
-        self._print_lines(lines, lineno, breaklist, self.curframe.f_lineno, -1)
+        self._print_lines(lines, lineno, breaklist, self.curframe)
     do_ll = do_longlist
 
     def do_source(self, arg):
@@ -1077,10 +1100,15 @@
         except (IOError, TypeError) as err:
             self.error(err)
             return
-        self._print_lines(lines, lineno, [], -1, -1)
+        self._print_lines(lines, lineno)
 
-    def _print_lines(self, lines, start, breaks, current, special):
+    def _print_lines(self, lines, start, breaks=(), frame=None):
         """Print a range of lines."""
+        if frame:
+            current_lineno = frame.f_lineno
+            exc_lineno = self.tb_lineno.get(frame, -1)
+        else:
+            current_lineno = exc_lineno = -1
         for lineno, line in enumerate(lines, start):
             s = str(lineno).rjust(3)
             if len(s) < 4:
@@ -1089,9 +1117,9 @@
                 s += 'B'
             else:
                 s += ' '
-            if lineno == current:
+            if lineno == current_lineno:
                 s += '->'
-            elif lineno == special:
+            elif lineno == exc_lineno:
                 s += '>>'
             self.message(s + '\t' + line.rstrip())
 

Modified: python/branches/py3k/Lib/test/test_pdb.py
==============================================================================
--- python/branches/py3k/Lib/test/test_pdb.py	(original)
+++ python/branches/py3k/Lib/test/test_pdb.py	Fri Jul 30 20:46:38 2010
@@ -359,6 +359,68 @@
     """
 
 
+def test_post_mortem():
+    """Test post mortem traceback debugging.
+
+    >>> def test_function_2():
+    ...     try:
+    ...         1/0
+    ...     finally:
+    ...         print('Exception!')
+
+    >>> def test_function():
+    ...     import pdb; pdb.Pdb().set_trace()
+    ...     test_function_2()
+    ...     print('Not reached.')
+
+    >>> with PdbTestInput([  # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+    ...     'next',      # step over exception-raising call
+    ...     'bt',        # get a backtrace
+    ...     'list',      # list code of test_function()
+    ...     'down',      # step into test_function_2()
+    ...     'list',      # list code of test_function_2()
+    ...     'continue',
+    ... ]):
+    ...    try:
+    ...        test_function()
+    ...    except ZeroDivisionError:
+    ...        print('Correctly reraised.')
+    > <doctest test.test_pdb.test_post_mortem[1]>(3)test_function()
+    -> test_function_2()
+    (Pdb) next
+    Exception!
+    ZeroDivisionError: division by zero
+    > <doctest test.test_pdb.test_post_mortem[1]>(3)test_function()
+    -> test_function_2()
+    (Pdb) bt
+    ...
+      <doctest test.test_pdb.test_post_mortem[2]>(10)<module>()
+    -> test_function()
+    > <doctest test.test_pdb.test_post_mortem[1]>(3)test_function()
+    -> test_function_2()
+      <doctest test.test_pdb.test_post_mortem[0]>(3)test_function_2()
+    -> 1/0
+    (Pdb) list
+      1         def test_function():
+      2             import pdb; pdb.Pdb().set_trace()
+      3  ->         test_function_2()
+      4             print('Not reached.')
+    [EOF]
+    (Pdb) down
+    > <doctest test.test_pdb.test_post_mortem[0]>(3)test_function_2()
+    -> 1/0
+    (Pdb) list
+      1         def test_function_2():
+      2             try:
+      3  >>             1/0
+      4             finally:
+      5  ->             print('Exception!')
+    [EOF]
+    (Pdb) continue
+    Correctly reraised.
+    """
+
+
 def test_pdb_skip_modules():
     """This illustrates the simple case of module skipping.
 

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Fri Jul 30 20:46:38 2010
@@ -475,6 +475,10 @@
 Library
 -------
 
+- For traceback debugging, the pdb listing now also shows the locations
+  where the exception was originally (re)raised, if it differs from the
+  last line executed (e.g. in case of finally clauses).
+
 - The pdb command "source" has been added.  It displays the source
   code for a given object, if possible.
 


More information about the Python-checkins mailing list