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.
This commit is contained in:
Jesse-Bakker 2017-11-30 00:05:07 +01:00 committed by Victor Stinner
parent 859f7ce7a4
commit 706e10b186
7 changed files with 70 additions and 26 deletions

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

View 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/

View 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 "a.py", line 2',
' <a.py, 2>',
' File "b.py", line 4',
' <b.py, 4>'])
[' 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, most_recent_first=True),
[' File "a.py", line 2',
' <a.py, 2>'])
self.assertEqual(tb.format(limit=-1, most_recent_first=True),
[' File "b.py", line 4',
' <b.py, 4>'])
class TestFilters(unittest.TestCase):

View 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)

View 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()

View 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))

View File

@ -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.)