[Python-checkins] cpython: Issue #22619: Added negative limit support in the traceback module.

serhiy.storchaka python-checkins at python.org
Sun May 3 12:32:20 CEST 2015


https://hg.python.org/cpython/rev/eb6052605fd8
changeset:   95863:eb6052605fd8
user:        Serhiy Storchaka <storchaka at gmail.com>
date:        Sun May 03 13:19:46 2015 +0300
summary:
  Issue #22619: Added negative limit support in the traceback module.
Based on patch by Dmitry Kazakov.

files:
  Doc/library/traceback.rst  |   52 ++++++---
  Lib/test/test_traceback.py |  127 ++++++++++++++++++++++++-
  Lib/traceback.py           |   14 +-
  Misc/ACKS                  |    1 +
  Misc/NEWS                  |    3 +
  5 files changed, 170 insertions(+), 27 deletions(-)


diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst
--- a/Doc/library/traceback.rst
+++ b/Doc/library/traceback.rst
@@ -22,15 +22,20 @@
 
 .. function:: print_tb(traceback, limit=None, file=None)
 
-   Print up to *limit* stack trace entries from *traceback*.  If *limit* is omitted
-   or ``None``, all entries are printed. If *file* is omitted or ``None``, the
-   output goes to ``sys.stderr``; otherwise it should be an open file or file-like
-   object to receive the output.
+   Print up to *limit* stack trace entries from *traceback* (starting from
+   the caller's frame) if *limit* is positive.  Otherwise, print the last
+   ``abs(limit)`` entries.  If *limit* is omitted or ``None``, all entries
+   are printed.  If *file* is omitted or ``None``, the output goes to
+   ``sys.stderr``; otherwise it should be an open file or file-like object
+   to receive the output.
+
+   .. versionchanged:: 3.5
+       Added negative *limit* support.
 
 
 .. function:: print_exception(type, value, traceback, limit=None, file=None, chain=True)
 
-   Print exception information and up to *limit* stack trace entries from
+   Print exception information and stack trace entries from
    *traceback* to *file*. This differs from :func:`print_tb` in the following
    ways:
 
@@ -41,6 +46,7 @@
      prints the line where the syntax error occurred with a caret indicating the
      approximate position of the error.
 
+   The optional *limit* argument has the same meaning as for :func:`print_tb`.
    If *chain* is true (the default), then chained exceptions (the
    :attr:`__cause__` or :attr:`__context__` attributes of the exception) will be
    printed as well, like the interpreter itself does when printing an unhandled
@@ -49,33 +55,41 @@
 
 .. function:: print_exc(limit=None, file=None, chain=True)
 
-   This is a shorthand for ``print_exception(*sys.exc_info())``.
+   This is a shorthand for ``print_exception(*sys.exc_info(), limit, file,
+   chain)``.
 
 
 .. function:: print_last(limit=None, file=None, chain=True)
 
    This is a shorthand for ``print_exception(sys.last_type, sys.last_value,
-   sys.last_traceback, limit, file)``.  In general it will work only after
-   an exception has reached an interactive prompt (see :data:`sys.last_type`).
+   sys.last_traceback, limit, file, chain)``.  In general it will work only
+   after an exception has reached an interactive prompt (see
+   :data:`sys.last_type`).
 
 
 .. function:: print_stack(f=None, limit=None, file=None)
 
-   This function prints a stack trace from its invocation point.  The optional *f*
-   argument can be used to specify an alternate stack frame to start.  The optional
-   *limit* and *file* arguments have the same meaning as for
-   :func:`print_exception`.
+   Print up to *limit* stack trace entries (starting from the invocation
+   point) if *limit* is positive.  Otherwise, print the last ``abs(limit)``
+   entries.  If *limit* is omitted or ``None``, all entries are printed.
+   The optional *f* argument can be used to specify an alternate stack frame
+   to start.  The optional *file* argument has the same meaning as for
+   :func:`print_tb`.
+
+   .. versionchanged:: 3.5
+          Added negative *limit* support.
 
 
 .. function:: extract_tb(traceback, limit=None)
 
-   Return a list of up to *limit* "pre-processed" stack trace entries extracted
-   from the traceback object *traceback*.  It is useful for alternate formatting of
-   stack traces.  If *limit* is omitted or ``None``, all entries are extracted.  A
-   "pre-processed" stack trace entry is a 4-tuple (*filename*, *line number*,
-   *function name*, *text*) representing the information that is usually printed
-   for a stack trace.  The *text* is a string with leading and trailing whitespace
-   stripped; if the source is not available it is ``None``.
+   Return a list of "pre-processed" stack trace entries extracted from the
+   traceback object *traceback*.  It is useful for alternate formatting of
+   stack traces.  The optional *limit* argument has the same meaning as for
+   :func:`print_tb`.  A "pre-processed" stack trace entry is a 4-tuple
+   (*filename*, *line number*, *function name*, *text*) representing the
+   information that is usually printed for a stack trace.  The *text* is a
+   string with leading and trailing whitespace stripped; if the source is
+   not available it is ``None``.
 
 
 .. function:: extract_stack(f=None, limit=None)
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -6,6 +6,7 @@
 import sys
 import unittest
 import re
+from test import support
 from test.support import TESTFN, Error, captured_output, unlink, cpython_only
 from test.script_helper import assert_python_ok
 import textwrap
@@ -453,6 +454,126 @@
         return s.getvalue()
 
 
+class LimitTests(unittest.TestCase):
+
+    ''' Tests for limit argument.
+        It's enough to test extact_tb, extract_stack and format_exception '''
+
+    def last_raises1(self):
+        raise Exception('Last raised')
+
+    def last_raises2(self):
+        self.last_raises1()
+
+    def last_raises3(self):
+        self.last_raises2()
+
+    def last_raises4(self):
+        self.last_raises3()
+
+    def last_raises5(self):
+        self.last_raises4()
+
+    def last_returns_frame1(self):
+        return sys._getframe()
+
+    def last_returns_frame2(self):
+        return self.last_returns_frame1()
+
+    def last_returns_frame3(self):
+        return self.last_returns_frame2()
+
+    def last_returns_frame4(self):
+        return self.last_returns_frame3()
+
+    def last_returns_frame5(self):
+        return self.last_returns_frame4()
+
+    def test_extract_stack(self):
+        frame = self.last_returns_frame5()
+        def extract(**kwargs):
+            return traceback.extract_stack(frame, **kwargs)
+        def assertEqualExcept(actual, expected, ignore):
+            self.assertEqual(actual[:ignore], expected[:ignore])
+            self.assertEqual(actual[ignore+1:], expected[ignore+1:])
+            self.assertEqual(len(actual), len(expected))
+
+        with support.swap_attr(sys, 'tracebacklimit', 1000):
+            nolim = extract()
+            self.assertGreater(len(nolim), 5)
+            self.assertEqual(extract(limit=2), nolim[-2:])
+            assertEqualExcept(extract(limit=100), nolim[-100:], -5-1)
+            self.assertEqual(extract(limit=-2), nolim[:2])
+            assertEqualExcept(extract(limit=-100), nolim[:100], len(nolim)-5-1)
+            self.assertEqual(extract(limit=0), [])
+            del sys.tracebacklimit
+            assertEqualExcept(extract(), nolim, -5-1)
+            sys.tracebacklimit = 2
+            self.assertEqual(extract(), nolim[-2:])
+            self.assertEqual(extract(limit=3), nolim[-3:])
+            self.assertEqual(extract(limit=-3), nolim[:3])
+            sys.tracebacklimit = 0
+            self.assertEqual(extract(), [])
+            sys.tracebacklimit = -1
+            self.assertEqual(extract(), [])
+
+    def test_extract_tb(self):
+        try:
+            self.last_raises5()
+        except Exception:
+            exc_type, exc_value, tb = sys.exc_info()
+        def extract(**kwargs):
+            return traceback.extract_tb(tb, **kwargs)
+
+        with support.swap_attr(sys, 'tracebacklimit', 1000):
+            nolim = extract()
+            self.assertEqual(len(nolim), 5+1)
+            self.assertEqual(extract(limit=2), nolim[:2])
+            self.assertEqual(extract(limit=10), nolim)
+            self.assertEqual(extract(limit=-2), nolim[-2:])
+            self.assertEqual(extract(limit=-10), nolim)
+            self.assertEqual(extract(limit=0), [])
+            del sys.tracebacklimit
+            self.assertEqual(extract(), nolim)
+            sys.tracebacklimit = 2
+            self.assertEqual(extract(), nolim[:2])
+            self.assertEqual(extract(limit=3), nolim[:3])
+            self.assertEqual(extract(limit=-3), nolim[-3:])
+            sys.tracebacklimit = 0
+            self.assertEqual(extract(), [])
+            sys.tracebacklimit = -1
+            self.assertEqual(extract(), [])
+
+    def test_format_exception(self):
+        try:
+            self.last_raises5()
+        except Exception:
+            exc_type, exc_value, tb = sys.exc_info()
+        # [1:-1] to exclude "Traceback (...)" header and
+        # exception type and value
+        def extract(**kwargs):
+            return traceback.format_exception(exc_type, exc_value, tb, **kwargs)[1:-1]
+
+        with support.swap_attr(sys, 'tracebacklimit', 1000):
+            nolim = extract()
+            self.assertEqual(len(nolim), 5+1)
+            self.assertEqual(extract(limit=2), nolim[:2])
+            self.assertEqual(extract(limit=10), nolim)
+            self.assertEqual(extract(limit=-2), nolim[-2:])
+            self.assertEqual(extract(limit=-10), nolim)
+            self.assertEqual(extract(limit=0), [])
+            del sys.tracebacklimit
+            self.assertEqual(extract(), nolim)
+            sys.tracebacklimit = 2
+            self.assertEqual(extract(), nolim[:2])
+            self.assertEqual(extract(limit=3), nolim[:3])
+            self.assertEqual(extract(limit=-3), nolim[-3:])
+            sys.tracebacklimit = 0
+            self.assertEqual(extract(), [])
+            sys.tracebacklimit = -1
+            self.assertEqual(extract(), [])
+
+
 class MiscTracebackCases(unittest.TestCase):
     #
     # Check non-printing functions in traceback module
@@ -592,16 +713,14 @@
                 traceback.walk_stack(None), capture_locals=True, limit=1)
         s = some_inner(3, 4)
         self.assertEqual(
-            ['  File "' + __file__ + '", line 592, '
-             'in some_inner\n'
+            ['  File "%s", line %d, in some_inner\n'
              '    traceback.walk_stack(None), capture_locals=True, limit=1)\n'
              '    a = 1\n'
              '    b = 2\n'
              '    k = 3\n'
-             '    v = 4\n'
+             '    v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 4)
             ], s.format())
 
-
 class TestTracebackException(unittest.TestCase):
 
     def test_smoke(self):
diff --git a/Lib/traceback.py b/Lib/traceback.py
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -1,8 +1,9 @@
 """Extract, format and print information about Python stack traces."""
 
+import collections
+import itertools
 import linecache
 import sys
-import operator
 
 __all__ = ['extract_stack', 'extract_tb', 'format_exception',
            'format_exception_only', 'format_list', 'format_stack',
@@ -315,12 +316,17 @@
         """
         if limit is None:
             limit = getattr(sys, 'tracebacklimit', None)
+            if limit is not None and limit < 0:
+                limit = 0
+        if limit is not None:
+            if limit >= 0:
+                frame_gen = itertools.islice(frame_gen, limit)
+            else:
+                frame_gen = collections.deque(frame_gen, maxlen=-limit)
 
         result = klass()
         fnames = set()
-        for pos, (f, lineno) in enumerate(frame_gen):
-            if limit is not None and pos >= limit:
-                break
+        for f, lineno in frame_gen:
             co = f.f_code
             filename = co.co_filename
             name = co.co_name
diff --git a/Misc/ACKS b/Misc/ACKS
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -712,6 +712,7 @@
 Lou Kates
 Makoto Kato
 Hiroaki Kawai
+Dmitry Kazakov
 Brian Kearns
 Sebastien Keim
 Ryan Kelly
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -25,6 +25,9 @@
 Library
 -------
 
+- Issue #22619: Added negative limit support in the traceback module.
+  Based on patch by Dmitry Kazakov.
+
 - Issue #24094: Fix possible crash in json.encode with poorly behaved dict
   subclasses.
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list