[Python-checkins] bpo-32121: Add most_recent_first parameter to tracemalloc.Traceback.format (#4534)

Victor Stinner webhook-mailer at python.org
Wed Nov 29 18:05:09 EST 2017


https://github.com/python/cpython/commit/706e10b186992e086e661a62d2c8ec9588525b31
commit: 706e10b186992e086e661a62d2c8ec9588525b31
branch: master
author: Jesse-Bakker <jessebakker00 at gmail.com>
committer: Victor Stinner <victor.stinner at gmail.com>
date: 2017-11-30T00:05:07+01:00
summary:

bpo-32121: Add most_recent_first parameter to tracemalloc.Traceback.format (#4534)

* 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.

files:
A Misc/NEWS.d/next/Library/2017-11-24-00-59-12.bpo-32121.ePbmwC.rst
M Doc/library/tracemalloc.rst
M Doc/whatsnew/3.7.rst
M Lib/test/test_tracemalloc.py
M Lib/test/test_warnings/__init__.py
M Lib/tracemalloc.py
M Lib/warnings.py

diff --git a/Doc/library/tracemalloc.rst b/Doc/library/tracemalloc.rst
index 048ee64aac9..2d327c02540 100644
--- a/Doc/library/tracemalloc.rst
+++ b/Doc/library/tracemalloc.rst
@@ -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.
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index e4600fe5ffd..5c001594dc8 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -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/
 
 
diff --git a/Lib/test/test_tracemalloc.py b/Lib/test/test_tracemalloc.py
index 533ba6dc460..b0a0e1b2d78 100644
--- a/Lib/test/test_tracemalloc.py
+++ b/Lib/test/test_tracemalloc.py
@@ -171,6 +171,9 @@ def allocate_bytes4(size):
 
         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 @@ def test_snapshot_group_by_cumulative(self):
     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 @@ def getline(filename, lineno):
                                  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):
diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py
index f2fdaa5386b..e60bc4d27f6 100644
--- a/Lib/test/test_warnings/__init__.py
+++ b/Lib/test/test_warnings/__init__.py
@@ -941,11 +941,11 @@ def func():
         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)
diff --git a/Lib/tracemalloc.py b/Lib/tracemalloc.py
index 597a2978afe..2c1ac3b39b0 100644
--- a/Lib/tracemalloc.py
+++ b/Lib/tracemalloc.py
@@ -171,16 +171,18 @@ def __repr__(self):
 @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 @@ def __str__(self):
     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()
diff --git a/Lib/warnings.py b/Lib/warnings.py
index d7ea057a688..4e7241fe6ca 100644
--- a/Lib/warnings.py
+++ b/Lib/warnings.py
@@ -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
index 00000000000..7701c86aa47
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-11-24-00-59-12.bpo-32121.ePbmwC.rst
@@ -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.)



More information about the Python-checkins mailing list