]> granicus.if.org Git - python/commitdiff
bpo-32121: Add most_recent_first parameter to tracemalloc.Traceback.format (#4534)
authorJesse-Bakker <jessebakker00@gmail.com>
Wed, 29 Nov 2017 23:05:07 +0000 (00:05 +0100)
committerVictor Stinner <victor.stinner@gmail.com>
Wed, 29 Nov 2017 23:05:07 +0000 (00:05 +0100)
* Add most_recent_first parameter to tracemalloc.Traceback.format to allow
   reversing the order of the frames in the output
* Reversed default sorting of tracemalloc.Traceback frames
* Allowed negative limit, truncating from the other side.

Doc/library/tracemalloc.rst
Doc/whatsnew/3.7.rst
Lib/test/test_tracemalloc.py
Lib/test/test_warnings/__init__.py
Lib/tracemalloc.py
Lib/warnings.py
Misc/NEWS.d/next/Library/2017-11-24-00-59-12.bpo-32121.ePbmwC.rst [new file with mode: 0644]

index 048ee64aac9851ae38482e51ffcb817abbab8843..2d327c02540995ebb1dcff9289166c33346429ac 100644 (file)
@@ -650,8 +650,8 @@ Traceback
 
 .. class:: Traceback
 
-   Sequence of :class:`Frame` instances sorted from the most recent frame to
-   the oldest frame.
+   Sequence of :class:`Frame` instances sorted from the oldest frame to the
+   most recent frame.
 
    A traceback contains at least ``1`` frame. If the ``tracemalloc`` module
    failed to get a frame, the filename ``"<unknown>"`` at line number ``0`` is
@@ -663,11 +663,17 @@ Traceback
    The :attr:`Trace.traceback` attribute is an instance of :class:`Traceback`
    instance.
 
-   .. method:: format(limit=None)
+   .. versionchanged:: 3.7
+      Frames are now sorted from the oldest to the most recent, instead of most recent to oldest.
 
-      Format the traceback as a list of lines with newlines.  Use the
-      :mod:`linecache` module to retrieve lines from the source code.  If
-      *limit* is set, only format the *limit* most recent frames.
+   .. method:: format(limit=None, most_recent_first=False)
+
+      Format the traceback as a list of lines with newlines. Use the
+      :mod:`linecache` module to retrieve lines from the source code.
+      If *limit* is set, format the *limit* most recent frames if *limit*
+      is positive. Otherwise, format the ``abs(limit)`` oldest frames.
+      If *most_recent_first* is ``True``, the order of the formatted frames
+      is reversed, returning the most recent frame first instead of last.
 
       Similar to the :func:`traceback.format_tb` function, except that
       :meth:`.format` does not include newlines.
index e4600fe5ffd316957737bd6c46a0d5784c6b4c38..5c001594dc830b7db048ce981f01288e45411e37 100644 (file)
@@ -751,6 +751,10 @@ Changes in the Python API
   avoid a warning escape them with a backslash.
   (Contributed by Serhiy Storchaka in :issue:`30349`.)
 
+* :class:`tracemalloc.Traceback` frames are now sorted from oldest to most
+  recent to be more consistent with :mod:`traceback`.
+  (Contributed by Jesse Bakker in :issue:`32121`.)
+
 .. _Unicode Technical Standard #18: https://unicode.org/reports/tr18/
 
 
index 533ba6dc460ec0c4ae62bce88f2c11d6d411730c..b0a0e1b2d7860a56e05bfc03c65affae563efd11 100644 (file)
@@ -171,6 +171,9 @@ class TestTracemallocEnabled(unittest.TestCase):
 
         traces = tracemalloc._get_traces()
 
+        obj1_traceback._frames = tuple(reversed(obj1_traceback._frames))
+        obj2_traceback._frames = tuple(reversed(obj2_traceback._frames))
+
         trace1 = self.find_trace(traces, obj1_traceback)
         trace2 = self.find_trace(traces, obj2_traceback)
         domain1, size1, traceback1 = trace1
@@ -537,11 +540,11 @@ class TestSnapshot(unittest.TestCase):
     def test_trace_format(self):
         snapshot, snapshot2 = create_snapshots()
         trace = snapshot.traces[0]
-        self.assertEqual(str(trace), 'a.py:2: 10 B')
+        self.assertEqual(str(trace), 'b.py:4: 10 B')
         traceback = trace.traceback
-        self.assertEqual(str(traceback), 'a.py:2')
+        self.assertEqual(str(traceback), 'b.py:4')
         frame = traceback[0]
-        self.assertEqual(str(frame), 'a.py:2')
+        self.assertEqual(str(frame), 'b.py:4')
 
     def test_statistic_format(self):
         snapshot, snapshot2 = create_snapshots()
@@ -574,17 +577,32 @@ class TestSnapshot(unittest.TestCase):
                                  side_effect=getline):
             tb = snapshot.traces[0].traceback
             self.assertEqual(tb.format(),
+                             ['  File "b.py", line 4',
+                              '    <b.py, 4>',
+                              '  File "a.py", line 2',
+                              '    <a.py, 2>'])
+
+            self.assertEqual(tb.format(limit=1),
+                             ['  File "a.py", line 2',
+                              '    <a.py, 2>'])
+
+            self.assertEqual(tb.format(limit=-1),
+                             ['  File "b.py", line 4',
+                              '    <b.py, 4>'])
+
+            self.assertEqual(tb.format(most_recent_first=True),
                              ['  File "a.py", line 2',
                               '    <a.py, 2>',
                               '  File "b.py", line 4',
                               '    <b.py, 4>'])
 
-            self.assertEqual(tb.format(limit=1),
+            self.assertEqual(tb.format(limit=1, most_recent_first=True),
                              ['  File "a.py", line 2',
                               '    <a.py, 2>'])
 
-            self.assertEqual(tb.format(limit=-1),
-                             [])
+            self.assertEqual(tb.format(limit=-1, most_recent_first=True),
+                             ['  File "b.py", line 4',
+                              '    <b.py, 4>'])
 
 
 class TestFilters(unittest.TestCase):
index f2fdaa5386b7d41236b6a7a5d90e0d6ff133b3a5..e60bc4d27f6678aa21ac6febb3f4b8d856a3a222 100644 (file)
@@ -941,11 +941,11 @@ class PyWarningsDisplayTests(WarningsDisplayTests, unittest.TestCase):
         expected = textwrap.dedent('''
             {fname}:5: ResourceWarning: unclosed file <...>
               f = None
-            Object allocated at (most recent call first):
-              File "{fname}", lineno 3
-                f = open(__file__)
+            Object allocated at (most recent call last):
               File "{fname}", lineno 7
                 func()
+              File "{fname}", lineno 3
+                f = open(__file__)
         ''')
         expected = expected.format(fname=support.TESTFN).strip()
         self.assertEqual(stderr, expected)
index 597a2978afe0cdb40d24d22c5a7e7cd39de8494b..2c1ac3b39b0784c1fc77572a91d38a76b3187a5d 100644 (file)
@@ -171,16 +171,18 @@ class Frame:
 @total_ordering
 class Traceback(Sequence):
     """
-    Sequence of Frame instances sorted from the most recent frame
-    to the oldest frame.
+    Sequence of Frame instances sorted from the oldest frame
+    to the most recent frame.
     """
     __slots__ = ("_frames",)
 
     def __init__(self, frames):
         Sequence.__init__(self)
         # frames is a tuple of frame tuples: see Frame constructor for the
-        # format of a frame tuple
-        self._frames = frames
+        # format of a frame tuple; it is reversed, because _tracemalloc
+        # returns frames sorted from most recent to oldest, but the
+        # Python API expects oldest to most recent
+        self._frames = tuple(reversed(frames))
 
     def __len__(self):
         return len(self._frames)
@@ -209,11 +211,19 @@ class Traceback(Sequence):
     def __repr__(self):
         return "<Traceback %r>" % (tuple(self),)
 
-    def format(self, limit=None):
+    def format(self, limit=None, most_recent_first=False):
         lines = []
-        if limit is not None and limit < 0:
-            return lines
-        for frame in self[:limit]:
+        if limit is not None:
+            if limit > 0:
+                frame_slice = self[-limit:]
+            else:
+                frame_slice = self[:limit]
+        else:
+            frame_slice = self
+
+        if most_recent_first:
+            frame_slice = reversed(frame_slice)
+        for frame in frame_slice:
             lines.append('  File "%s", line %s'
                          % (frame.filename, frame.lineno))
             line = linecache.getline(frame.filename, frame.lineno).strip()
index d7ea057a688ab12e594fdd58490de456940cf0dc..4e7241fe6ca450a11ce2c32a5db59789fd74f82d 100644 (file)
@@ -62,7 +62,7 @@ def _formatwarnmsg_impl(msg):
             tb = None
 
         if tb is not None:
-            s += 'Object allocated at (most recent call first):\n'
+            s += 'Object allocated at (most recent call last):\n'
             for frame in tb:
                 s += ('  File "%s", lineno %s\n'
                       % (frame.filename, frame.lineno))
diff --git a/Misc/NEWS.d/next/Library/2017-11-24-00-59-12.bpo-32121.ePbmwC.rst b/Misc/NEWS.d/next/Library/2017-11-24-00-59-12.bpo-32121.ePbmwC.rst
new file mode 100644 (file)
index 0000000..7701c86
--- /dev/null
@@ -0,0 +1,6 @@
+Made ``tracemalloc.Traceback`` behave more like the traceback module, 
+sorting the frames from oldest to most recent. ``Traceback.format()`` 
+now accepts negative *limit*, truncating the result to the ``abs(limit)`` 
+oldest frames. To get the old behaviour, one can use the new 
+*most_recent_first* argument to ``Traceback.format()``.
+(Patch by Jesse Bakker.)