Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions Doc/library/traceback.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The module defines the following functions:
Added negative *limit* support.


.. function:: print_exception(etype, value, tb, limit=None, file=None, chain=True)
.. function:: print_exception(etype, value, tb, limit=None, file=None, chain=True, filter_frames=None)

Print exception information and stack trace entries from traceback object
*tb* to *file*. This differs from :func:`print_tb` in the following
Expand All @@ -53,24 +53,29 @@ The module defines the following functions:
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
exception.
exception. If *filter_frames* is not ``None``, it should be a callable
that accepts a single frame object. Only traceback entries for which
*filter_frames* returns ``True`` will be displayed.

.. versionchanged:: 3.5
The *etype* argument is ignored and inferred from the type of *value*.

.. versionadded:: 3.7
*filter_frames* argument.

.. function:: print_exc(limit=None, file=None, chain=True)

.. function:: print_exc(limit=None, file=None, chain=True, filter_frames=None)

This is a shorthand for ``print_exception(*sys.exc_info(), limit, file,
chain)``.
chain, filter_frames)``.


.. function:: print_last(limit=None, file=None, chain=True)
.. function:: print_last(limit=None, file=None, chain=True, filter_frames=None)

This is a shorthand for ``print_exception(sys.last_type, sys.last_value,
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`).
sys.last_traceback, limit, file, chain, filter_frames)``.
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)
Expand Down Expand Up @@ -126,7 +131,7 @@ The module defines the following functions:
which exception occurred is the always last string in the list.


.. function:: format_exception(etype, value, tb, limit=None, chain=True)
.. function:: format_exception(etype, value, tb, limit=None, chain=True, filter_frames=None)

Format a stack trace and the exception information. The arguments have the
same meaning as the corresponding arguments to :func:`print_exception`. The
Expand All @@ -138,10 +143,10 @@ The module defines the following functions:
The *etype* argument is ignored and inferred from the type of *value*.


.. function:: format_exc(limit=None, chain=True)
.. function:: format_exc(limit=None, chain=True, filter_frames=None)

This is like ``print_exc(limit)`` but returns a string instead of printing to
a file.
This is like ``print_exc(limit, chain, filter_frames)`` but returns a string
instead of printing to a file.


.. function:: format_tb(tb, limit=None)
Expand Down Expand Up @@ -239,12 +244,14 @@ capture data for later printing in a lightweight fashion.

Note that when locals are captured, they are also shown in the traceback.

.. method:: format(*, chain=True)
.. method:: format(*, chain=True, filter_frames=None)

Format the exception.

If *chain* is not ``True``, ``__cause__`` and ``__context__`` will not
be formatted.
be formatted. If *filter_frames* is not ``None``, it should be a callable
that accepts a single frame object. Only traceback entries for which
*filter_frames* returns ``True`` will be returned.

The return value is a generator of strings, each ending in a newline and
some containing internal newlines. :func:`~traceback.print_exception`
Expand All @@ -253,6 +260,9 @@ capture data for later printing in a lightweight fashion.
The message indicating which exception occurred is always the last
string in the output.

.. versionadded:: 3.7
*filter_frames* argument.

.. method:: format_exception_only()

Format the exception part of the traceback.
Expand Down
32 changes: 32 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,38 @@ def test_traceback_header(self):
exc = traceback.TracebackException(Exception, Exception("haven"), None)
self.assertEqual(list(exc.format()), ["Exception: haven\n"])

def test_filtering(self):
def a():
b()

def b():
c()

def c():
try:
d()
except:
raise TypeError

def d():
1/0

try:
a()
except Exception:
exc_info = sys.exc_info()
exc = traceback.TracebackException(*exc_info)
full = list(exc.format())

# Must only remove the current frame
current = sys._getframe()
filtered = exc.format(chain=False, filter_frames=lambda f: f is not current)
self.assertEqual(list(filtered), [full[-6]] + full[-4:])

# Must remove c frames from all chained exceptions
filtered = exc.format(filter_frames=lambda f: f.f_code.co_name != 'c')
self.assertEqual(list(filtered), full[:1] + full[2:9] + full[10:])


class MiscTest(unittest.TestCase):

Expand Down
85 changes: 55 additions & 30 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def extract_tb(tb, limit=None):
"another exception occurred:\n\n")


def print_exception(etype, value, tb, limit=None, file=None, chain=True):
def print_exception(etype, value, tb, limit=None, file=None, chain=True, filter_frames=None):
"""Print exception up to 'limit' stack trace entries from 'tb' to 'file'.

This differs from print_tb() in the following ways: (1) if
Expand All @@ -96,12 +96,12 @@ def print_exception(etype, value, tb, limit=None, file=None, chain=True):
# ignore it here (rather than in the new TracebackException API).
if file is None:
file = sys.stderr
for line in TracebackException(
type(value), value, tb, limit=limit).format(chain=chain):
exc = TracebackException(type(value), value, tb, limit=limit)
for line in exc.format(chain=chain, filter_frames=filter_frames):
print(line, file=file, end="")


def format_exception(etype, value, tb, limit=None, chain=True):
def format_exception(etype, value, tb, limit=None, chain=True, filter_frames=None):
"""Format a stack trace and the exception information.

The arguments have the same meaning as the corresponding arguments
Expand All @@ -113,8 +113,8 @@ def format_exception(etype, value, tb, limit=None, chain=True):
# format_exception has ignored etype for some time, and code such as cgitb
# passes in bogus values as a result. For compatibility with such code we
# ignore it here (rather than in the new TracebackException API).
return list(TracebackException(
type(value), value, tb, limit=limit).format(chain=chain))
exc = TracebackException(type(value), value, tb, limit=limit)
return list(exc.format(chain=chain, filter_frames=filter_frames))


def format_exception_only(etype, value):
Expand Down Expand Up @@ -154,21 +154,24 @@ def _some_str(value):

# --

def print_exc(limit=None, file=None, chain=True):
"""Shorthand for 'print_exception(*sys.exc_info(), limit, file)'."""
print_exception(*sys.exc_info(), limit=limit, file=file, chain=chain)
def print_exc(limit=None, file=None, chain=True, filter_frames=None):
"""Shorthand for 'print_exception(*sys.exc_info(), limit, file,
chain, filter_frames)'."""
print_exception(*sys.exc_info(), limit=limit, file=file,
chain=chain, filter_frames=filter_frames)

def format_exc(limit=None, chain=True):
def format_exc(limit=None, chain=True, filter_frames=None):
"""Like print_exc() but return a string."""
return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain))
return "".join(format_exception(*sys.exc_info(), limit=limit,
chain=chain, filter_frames=filter_frames))

def print_last(limit=None, file=None, chain=True):
def print_last(limit=None, file=None, chain=True, filter_frames=None):
"""This is a shorthand for 'print_exception(sys.last_type,
sys.last_value, sys.last_traceback, limit, file)'."""
sys.last_value, sys.last_traceback, limit, file, chain, filter_frames)'."""
if not hasattr(sys, "last_type"):
raise ValueError("no last exception")
print_exception(sys.last_type, sys.last_value, sys.last_traceback,
limit, file, chain)
limit, file, chain, filter_frames)

#
# Printing and Extracting Stacks.
Expand Down Expand Up @@ -306,6 +309,20 @@ def walk_tb(tb):
tb = tb.tb_next


def _walk_with_limit(frame_gen, limit=None):
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:
return itertools.islice(frame_gen, limit)
else:
return collections.deque(frame_gen, maxlen=-limit)

return frame_gen


class StackSummary(list):
"""A stack of frames."""

Expand All @@ -323,19 +340,10 @@ def extract(klass, frame_gen, *, limit=None, lookup_lines=True,
:param capture_locals: If True, the local variables from each frame will
be captured as object representations into the FrameSummary.
"""
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 f, lineno in frame_gen:

for f, lineno in _walk_with_limit(frame_gen, limit):
co = f.f_code
filename = co.co_filename
name = co.co_name
Expand Down Expand Up @@ -491,8 +499,10 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
self.__suppress_context__ = \
exc_value.__suppress_context__ if exc_value else False
# TODO: locals.
# Keep actual frame objects for filtering
self._raw_stack = list(_walk_with_limit(walk_tb(exc_traceback), limit=limit))
self.stack = StackSummary.extract(
walk_tb(exc_traceback), limit=limit, lookup_lines=lookup_lines,
self._raw_stack, limit=limit, lookup_lines=lookup_lines,
capture_locals=capture_locals)
self.exc_type = exc_type
# Capture now to permit freeing resources: only complication is in the
Expand Down Expand Up @@ -573,11 +583,15 @@ def format_exception_only(self):
msg = self.msg or "<no detail available>"
yield "{}: {}\n".format(stype, msg)

def format(self, *, chain=True):
def format(self, *, chain=True, filter_frames=None):
"""Format the exception.

If chain is not *True*, *__cause__* and *__context__* will not be formatted.

If filter_frames is not *None*, it should be a callable that accepts a
single frame object. Only traceback entries for which filter_frames returns
*True* will be returned.

The return value is a generator of strings, each ending in a newline and
some containing internal newlines. `print_exception` is a wrapper around
this method which just prints the lines to a file.
Expand All @@ -587,13 +601,24 @@ def format(self, *, chain=True):
"""
if chain:
if self.__cause__ is not None:
yield from self.__cause__.format(chain=chain)
yield from self.__cause__.format(chain=chain, filter_frames=filter_frames)
yield _cause_message
elif (self.__context__ is not None and
not self.__suppress_context__):
yield from self.__context__.format(chain=chain)
yield from self.__context__.format(chain=chain, filter_frames=filter_frames)
yield _context_message
if self.exc_traceback is not None:
yield 'Traceback (most recent call last):\n'
yield from self.stack.format()

if filter_frames is not None:
keep = []
for (frame, _), summary in zip(self._raw_stack, self.stack):
if filter_frames(frame):
keep.append(summary)

clean = StackSummary.from_list(keep)
yield from clean.format()
else:
yield from self.stack.format()

yield from self.format_exception_only()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
``traceback.TracebackException.format`` and :mod:`traceback` functions that
use it now accept the callable ``filter_frames`` argument for hiding
unwanted traceback entries.