diff -r 3f3cbfd52f94 Doc/library/traceback.rst --- a/Doc/library/traceback.rst Tue Jul 09 14:30:22 2013 +0200 +++ b/Doc/library/traceback.rst Fri Jul 12 16:13:17 2013 +0100 @@ -61,12 +61,34 @@ .. 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 + Print 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`. +.. function:: extract_exception(type, value, traceback, limit=None, chain=True) + + Extract exception and traceback information and return a summary. The summary + object contains the following attributes: + + * :attr:`exc_type` is the exception type + * :attr:`exc_str` is the string representation of the exception + * :attr:`exc_traceback` is a list of tuples as returned by :func:`extract_tb`, + up to *limit* entries. + * :attr:`exc_offset` for a :exc:`SyntaxError` is the line position where the + error occurred. + * :attr:`cause` is a summary object for the exception cause + * :attr:`context` is a summary object for the exception context. This will + contain a value even if :attr:`cause` is set, unless the context was + explicitly suppressed by using ``raise Exception as None``. + + :attr:`cause` and :attr:`context` are only set if *chain* is true (the + default). + + .. versionadded:: 3.4 + + .. function:: extract_tb(traceback, limit=None) Return a list of up to *limit* "pre-processed" stack trace entries extracted @@ -82,7 +104,9 @@ Extract the raw traceback from the current stack frame. The return value has the same format as for :func:`extract_tb`. The optional *f* and *limit* - arguments have the same meaning as for :func:`print_stack`. + arguments have the same meaning as for :func:`print_stack`. Each item in + the list is a quadruple (*filename*, *line number*, *function name*, *text*), + and the entries are in order from oldest to newest stack frame. .. function:: format_list(list) diff -r 3f3cbfd52f94 Lib/test/test_traceback.py --- a/Lib/test/test_traceback.py Tue Jul 09 14:30:22 2013 +0200 +++ b/Lib/test/test_traceback.py Fri Jul 12 16:13:17 2013 +0100 @@ -83,6 +83,16 @@ err = traceback.format_exception_only(None, None) self.assertEqual(err, ['None\n']) + err = traceback.format_exception(None, None, None) + self.assertEqual(err, ['None\n']) + + summary = traceback.extract_exception(None, None, None) + self.assertIsNone(summary.exc_type) + self.assertEqual(summary.exc_str, '') + self.assertEqual(summary.exc_traceback, []) + err = list(summary.format_exception()) + self.assertEqual(err, ['None\n']) + def test_encoded_file(self): # Test that tracebacks are correctly printed for encoded source files: # - correct line number (Issue2384) @@ -239,41 +249,105 @@ 1/0 # Marker except ZeroDivisionError as _: e = _ - lines = self.get_report(e).splitlines() + report = self.get_report(e) + lines = report.splitlines() self.assertEqual(len(lines), 4) self.assertTrue(lines[0].startswith('Traceback')) self.assertTrue(lines[1].startswith(' File')) self.assertIn('1/0 # Marker', lines[2]) self.assertTrue(lines[3].startswith('ZeroDivisionError')) + summary = traceback.extract_exception(type(e), e, None) + self.assertIs(summary.exc_type, ZeroDivisionError) + self.assertEqual(summary.exc_str, "division by zero") + self.assertEqual(len(summary.exc_traceback), 1) + self.assertEqual(summary.exc_traceback[0][0], __file__) + self.assertEqual(summary.exc_traceback[0][2], 'test_simple') + self.assertEqual(summary.exc_traceback[0][3], '1/0 # Marker') + + self.assertEqual(''.join(summary.format_exception()), report) + + def test_deferred_traceback(self): + def inner_raise(): + try: + self.zero_div() + except ZeroDivisionError as err: + raise KeyError from err + try: + inner_raise() # Marker + except KeyError as _: + e = _ + report = self.get_report(e) + + summary = traceback.extract_exception(type(e), e, None, defer=True) + self.assertEqual(len(summary.exc_traceback), 2) + tb = summary.exc_traceback + self.assertIsInstance(tb[0][3], traceback.DeferredText) + self.assertEqual(str(tb[0][3]), 'inner_raise() # Marker') + + self.assertEqual(''.join(summary.format_exception()), report) + def test_cause(self): def inner_raise(): try: self.zero_div() - except ZeroDivisionError as e: - raise KeyError from e - def outer_raise(): + except ZeroDivisionError as err: + raise KeyError from err + try: inner_raise() # Marker - blocks = boundaries.split(self.get_report(outer_raise)) + except KeyError as _: + e = _ + report = self.get_report(e) + blocks = boundaries.split(report) self.assertEqual(len(blocks), 3) self.assertEqual(blocks[1], cause_message) self.check_zero_div(blocks[0]) self.assertIn('inner_raise() # Marker', blocks[2]) + summary = traceback.extract_exception(type(e), e, None) + self.assertIs(summary.exc_type, KeyError) + self.assertEqual(summary.exc_str, "") + self.assertEqual(len(summary.exc_traceback), 2) + tb = summary.exc_traceback + self.assertEqual(tb[0][0], __file__) + self.assertEqual(tb[0][2], 'test_cause') + self.assertEqual(tb[0][3], 'inner_raise() # Marker') + self.assertEqual(tb[1][0], __file__) + self.assertEqual(tb[1][2], 'inner_raise') + self.assertEqual(tb[1][3], 'raise KeyError from err') + + self.assertIsNot(summary.context, None) + self.assertIs(summary.cause, summary.context) + cause_summary = summary.cause + self.assertIs(cause_summary.exc_type, ZeroDivisionError) + self.assertEqual(cause_summary.exc_str, "division by zero") + self.assertEqual(len(cause_summary.exc_traceback), 2) + + self.assertEqual(''.join(summary.format_exception()), report) + def test_context(self): def inner_raise(): try: self.zero_div() except ZeroDivisionError: raise KeyError - def outer_raise(): + try: inner_raise() # Marker - blocks = boundaries.split(self.get_report(outer_raise)) + except KeyError as _: + e = _ + report = self.get_report(e) + blocks = boundaries.split(report) self.assertEqual(len(blocks), 3) self.assertEqual(blocks[1], context_message) self.check_zero_div(blocks[0]) self.assertIn('inner_raise() # Marker', blocks[2]) + summary = traceback.extract_exception(type(e), e, None) + self.assertIsNot(summary.context, None) + self.assertIs(summary.cause, None) + + self.assertEqual(''.join(summary.format_exception()), report) + def test_context_suppression(self): try: try: @@ -282,13 +356,21 @@ raise ZeroDivisionError from None except ZeroDivisionError as _: e = _ - lines = self.get_report(e).splitlines() + report = self.get_report(e) + lines = report.splitlines() self.assertEqual(len(lines), 4) self.assertTrue(lines[0].startswith('Traceback')) self.assertTrue(lines[1].startswith(' File')) self.assertIn('ZeroDivisionError from None', lines[2]) self.assertTrue(lines[3].startswith('ZeroDivisionError')) + self.assertIsNotNone(e.__context__) + summary = traceback.extract_exception(type(e), e, None) + self.assertIs(summary.cause, None) + self.assertIs(summary.context, None) + + self.assertEqual(''.join(summary.format_exception()), report) + def test_cause_and_context(self): # When both a cause and a context are set, only the cause should be # displayed and the context should be muted. @@ -301,14 +383,27 @@ xyzzy except NameError: raise KeyError from e - def outer_raise(): + try: inner_raise() # Marker - blocks = boundaries.split(self.get_report(outer_raise)) + except KeyError as _: + err = _ + report = self.get_report(err) + blocks = boundaries.split(report) self.assertEqual(len(blocks), 3) self.assertEqual(blocks[1], cause_message) self.check_zero_div(blocks[0]) self.assertIn('inner_raise() # Marker', blocks[2]) + summary = traceback.extract_exception(type(err), err, None) + self.assertIsNot(summary.context, None) + self.assertIsNot(summary.cause, None) + self.assertIs(summary.exc_type, KeyError) + self.assertIs(summary.cause.exc_type, ZeroDivisionError) + # The context is set even though it will be suppressed in the output + self.assertIs(summary.context.exc_type, NameError) + + self.assertEqual(''.join(summary.format_exception()), report) + def test_cause_recursive(self): def inner_raise(): try: @@ -319,9 +414,12 @@ raise KeyError from e except KeyError as e: raise z from e - def outer_raise(): + try: inner_raise() # Marker - blocks = boundaries.split(self.get_report(outer_raise)) + except ZeroDivisionError as _: + err = _ + report = self.get_report(err) + blocks = boundaries.split(report) self.assertEqual(len(blocks), 3) self.assertEqual(blocks[1], cause_message) # The first block is the KeyError raised from the ZeroDivisionError @@ -332,17 +430,39 @@ self.assertIn('inner_raise() # Marker', blocks[2]) self.check_zero_div(blocks[2]) + summary = traceback.extract_exception(type(err), err, None) + self.assertIsNot(summary.context, None) + self.assertIs(summary.cause, summary.context) + + self.assertIs(summary.cause.cause, summary) + self.assertIs(summary.context.context, None) + + self.assertEqual(''.join(summary.format_exception()), report) + def test_syntax_error_offset_at_eol(self): # See #10186. def e(): raise SyntaxError('', ('', 0, 5, 'hello')) msg = self.get_report(e).splitlines() self.assertEqual(msg[-2], " ^") - def e(): + try: exec("x = 5 | 4 |") - msg = self.get_report(e).splitlines() + except SyntaxError as _: + e = _ + report = self.get_report(e) + msg = report.splitlines() self.assertEqual(msg[-2], ' ^') + summary = traceback.extract_exception(type(e), e, None) + self.assertEqual(len(summary.exc_traceback), 2) + tb = summary.exc_traceback + self.assertEqual(tb[0][0], __file__) + + self.assertEqual(tb[1][0], '') + self.assertEqual(tb[1][2], '') + self.assertEqual(tb[1][3], 'x = 5 | 4 |\n') + + self.assertEqual(''.join(summary.format_exception()), report) class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase): # diff -r 3f3cbfd52f94 Lib/traceback.py --- a/Lib/traceback.py Tue Jul 09 14:30:22 2013 +0200 +++ b/Lib/traceback.py Fri Jul 12 16:13:17 2013 +0100 @@ -4,30 +4,38 @@ import sys import operator -__all__ = ['extract_stack', 'extract_tb', 'format_exception', - 'format_exception_only', 'format_list', 'format_stack', - 'format_tb', 'print_exc', 'format_exc', 'print_exception', - 'print_last', 'print_stack', 'print_tb'] +__all__ = ['extract_exception', 'extract_stack', 'extract_tb', + 'format_exception', 'format_exception_only', 'format_list', + 'format_stack', 'format_tb', 'print_exc', 'format_exc', + 'print_exception', 'print_last', 'print_stack', 'print_tb'] # # Formatting and printing lists of traceback lines. # -def _format_list_iter(extracted_list): +def _format_traceback(extracted_list): for filename, lineno, name, line in extracted_list: - item = ' File "{}", line {}, in {}\n'.format(filename, lineno, name) - if line: - item = item + ' {}\n'.format(line.strip()) + if name: + item = ' File "{}", line {}, in {}\n'.format( + filename, lineno, name) + else: + item = ' File "{}", line {}\n'.format(filename, lineno) + linestr = str(line) # Evaluate deferred traceback lines + if line is not None and linestr: + item += ' {}\n'.format(linestr.strip()) yield item + def print_list(extracted_list, file=None): """Print the list of tuples as returned by extract_tb() or - extract_stack() as a formatted stack trace to the given file.""" + extract_stack() as a formatted stack trace to the given file. + """ if file is None: file = sys.stderr - for item in _format_list_iter(extracted_list): + for item in _format_traceback(extracted_list): print(item, file=file, end="") + def format_list(extracted_list): """Format a list of traceback entry tuples for printing. @@ -38,44 +46,72 @@ the strings may contain internal newlines as well, for those items whose source text line is not None. """ - return list(_format_list_iter(extracted_list)) + return list(_format_traceback(extracted_list)) # # Printing and Extracting Tracebacks. # -# extractor takes curr and needs to return a tuple of: -# - Frame object -# - Line number -# - Next item (same type as curr) -# In practice, curr is either a traceback or a frame. -def _extract_tb_or_stack_iter(curr, limit, extractor): +class DeferredText: + """Wrap the necessary information for retrieving the text of a traceback + line at a later time. + """ + __slots__ = ('filename', 'lineno', 'module_globals') + + def __init__(self, filename, lineno, module_globals): + self.filename = filename + self.lineno = lineno + self.module_globals = None + if '__loader__' in module_globals: + self.module_globals = { + '__loader__': module_globals['__loader__'], + '__name__': module_globals['__name__']} + + def __str__(self): + if self.filename: + linecache.checkcache(self.filename) + line = linecache.getline(self.filename, self.lineno, + self.module_globals) + + if line: + return line.strip() + return '' + + +def _extract_tb_tuples(frames, limit, defer=False): if limit is None: limit = getattr(sys, 'tracebacklimit', None) + checked = set() n = 0 - while curr is not None and (limit is None or n < limit): - f, lineno, next_item = extractor(curr) + for f, lineno in frames: + if limit is not None and n >= limit: + break co = f.f_code filename = co.co_filename name = co.co_name + if defer: + line = DeferredText(filename, lineno, f.f_globals) + else: + if filename and filename not in checked: + linecache.checkcache(filename) + checked.add(filename) + line = linecache.getline(filename, lineno, f.f_globals) - linecache.checkcache(filename) - line = linecache.getline(filename, lineno, f.f_globals) - - if line: - line = line.strip() - else: - line = None + if line: + line = line.strip() + else: + line = None yield (filename, lineno, name, line) - curr = next_item n += 1 -def _extract_tb_iter(tb, limit): - return _extract_tb_or_stack_iter( - tb, limit, - operator.attrgetter("tb_frame", "tb_lineno", "tb_next")) + +def _iter_tb(tb): + while tb is not None: + yield tb.tb_frame, tb.tb_lineno + tb = tb.tb_next + def print_tb(tb, limit=None, file=None): """Print up to 'limit' stack trace entries from the traceback 'tb'. @@ -87,11 +123,13 @@ """ print_list(extract_tb(tb, limit=limit), file=file) + def format_tb(tb, limit=None): """A shorthand for 'format_list(extract_tb(tb, limit)).""" return format_list(extract_tb(tb, limit=limit)) -def extract_tb(tb, limit=None): + +def extract_tb(tb, limit=None, defer=False): """Return list of up to limit pre-processed entries from traceback. This is useful for alternate formatting of stack traces. If @@ -100,9 +138,10 @@ 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. + available it is None. If 'defer' is true, line information + will not be extracted until the traceback is formatted. """ - return list(_extract_tb_iter(tb, limit=limit)) + return list(_extract_tb_tuples(_iter_tb(tb), limit=limit, defer=defer)) # # Exception formatting and output. @@ -110,11 +149,15 @@ _cause_message = ( "\nThe above exception was the direct cause " - "of the following exception:\n") + "of the following exception:\n\n") _context_message = ( "\nDuring handling of the above exception, " - "another exception occurred:\n") + "another exception occurred:\n\n") + +_traceback_message = ( + 'Traceback (most recent call last):\n') + def _iter_chain(exc, custom_tb=None, seen=None): if seen is None: @@ -124,20 +167,23 @@ context = exc.__context__ cause = exc.__cause__ if cause is not None and cause not in seen: - its.append(_iter_chain(cause, False, seen)) + its.append(_iter_chain(cause, None, seen)) its.append([(_cause_message, None)]) elif (context is not None and not exc.__suppress_context__ and context not in seen): its.append(_iter_chain(context, None, seen)) its.append([(_context_message, None)]) - its.append([(exc, custom_tb or exc.__traceback__)]) + if custom_tb is None: + custom_tb = exc.__traceback__ + its.append([(exc, custom_tb)]) # itertools.chain is in an extension module and may be unavailable for it in its: yield from it -def _format_exception_iter(etype, value, tb, limit, chain): - if chain: + +def _format_exception_iter(etype, value, tb, limit=None, chain=True): + if chain and value is not None: values = _iter_chain(value, tb) else: values = [(value, tb)] @@ -145,63 +191,21 @@ for value, tb in values: if isinstance(value, str): # This is a cause/context message line - yield value + '\n' + yield value continue if tb: - yield 'Traceback (most recent call last):\n' - yield from _format_list_iter(_extract_tb_iter(tb, limit=limit)) + yield _traceback_message + yield from _format_traceback(_extract_tb_tuples(_iter_tb(tb), + limit=limit)) yield from _format_exception_only_iter(type(value), value) -def print_exception(etype, value, tb, limit=None, file=None, chain=True): - """Print exception up to 'limit' stack trace entries from 'tb' to 'file'. - - This differs from print_tb() in the following ways: (1) if - traceback is not None, it prints a header "Traceback (most recent - call last):"; (2) it prints the exception type and value after the - stack trace; (3) if type is SyntaxError and value has the - appropriate format, it prints the line where the syntax error - occurred with a caret on the next line indicating the approximate - position of the error. - """ - if file is None: - file = sys.stderr - for line in _format_exception_iter(etype, value, tb, limit, chain): - print(line, file=file, end="") - -def format_exception(etype, value, tb, limit=None, chain=True): - """Format a stack trace and the exception information. - - The arguments have the same meaning as the corresponding arguments - to print_exception(). The return value is a list of strings, each - ending in a newline and some containing internal newlines. When - these lines are concatenated and printed, exactly the same text is - printed as does print_exception(). - """ - return list(_format_exception_iter(etype, value, tb, limit, chain)) - -def format_exception_only(etype, value): - """Format the exception part of a traceback. - - The arguments are the exception type and value such as given by - sys.last_type and sys.last_value. The return value is a list of - strings, each ending in a newline. - - Normally, the list contains a single string; however, for - SyntaxError exceptions, it contains several lines that (when - printed) display detailed information about where the syntax - error occurred. - - The message indicating which exception occurred is always the last - string in the list. - - """ - return list(_format_exception_only_iter(etype, value)) def _format_exception_only_iter(etype, value): # Gracefully handle (the way Python 2.4 and earlier did) the case of # being called with (None, None). - if etype is None: - yield _format_final_exc_line(etype, value) + if etype is None or etype is type(None): + + yield 'None\n' return stype = etype.__name__ @@ -210,7 +214,11 @@ stype = smod + '.' + stype if not issubclass(etype, SyntaxError): - yield _format_final_exc_line(stype, value) + valuestr = _format_value(value) + if not valuestr: + yield "%s\n" % stype + else: + yield "%s: %s\n" % (stype, valuestr) return # It was a syntax error; show exactly where the problem was found. @@ -231,49 +239,218 @@ msg = value.msg or "" yield "{}: {}\n".format(stype, msg) -def _format_final_exc_line(etype, value): - valuestr = _some_str(value) - if value is None or not valuestr: - line = "%s\n" % etype - else: - line = "%s: %s\n" % (etype, valuestr) - return line -def _some_str(value): +def _format_value(value): + if value is None: + return '' try: return str(value) except: return '' % type(value).__name__ + +def print_exception(etype, value, tb, limit=None, file=None, chain=True): + """Print exception up to 'limit' stack trace entries from 'tb' to 'file'. + + This differs from print_tb() in the following ways: (1) if + traceback is not None, it prints a header "Traceback (most recent + call last):"; (2) it prints the exception type and value after the + stack trace; (3) if type is SyntaxError and value has the + appropriate format, it prints the line where the syntax error + occurred with a caret on the next line indicating the approximate + position of the error. + + If 'chain' is true (the default), then chained exceptions (the + __cause__ and __context__ attributes of the exception) will be + printed as well, like the interpreter itself does when printing an + unhandled exception. + """ + if file is None: + file = sys.stderr + for line in _format_exception_iter(etype, value, tb, limit, chain): + print(line, file=file, end="") + + +def format_exception(etype, value, tb, limit=None, chain=True): + """Format a stack trace and the exception information. + + The arguments have the same meaning as the corresponding arguments + to print_exception(). The return value is a list of strings, each + ending in a newline and some containing internal newlines. When + these lines are concatenated and printed, exactly the same text is + printed as does print_exception(). + """ + return list(_format_exception_iter(etype, value, tb, limit, chain)) + + +def format_exception_only(etype, value): + """Format the exception part of a traceback. + + The arguments are the exception type and value such as given by + sys.last_type and sys.last_value. The return value is a list of + strings, each ending in a newline. + + Normally, the list contains a single string; however, for + SyntaxError exceptions, it contains several lines that (when + printed) display detailed information about where the syntax + error occurred. + + The message indicating which exception occurred is always the last + string in the list. + """ + return list(_format_exception_only_iter(etype, 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 format_exc(limit=None, chain=True): """Like print_exc() but return a string.""" return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain)) + def 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)'.""" + sys.last_value, sys.last_traceback, limit, file)'. + """ 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) + +class ExceptionSummary: + """Summary object containing all relevant information to format an + exception description. + """ + __slots__ = ('exc_type', 'exc_str', 'exc_traceback', 'exc_offset', + 'cause', 'context') + + def __init__(self, value, tb, limit=None, defer=False): + self.exc_type = type(value) if value is not None else None + self.exc_str = _format_value(value) + self.exc_traceback = extract_tb(tb, limit, defer=defer) + self.exc_offset = None + + if isinstance(value, SyntaxError): + badline = value.text + filename = value.filename or "" + lineno = str(value.lineno) or '?' + + self.exc_traceback.append((filename, lineno, '', badline)) + self.exc_offset = value.offset + self.exc_str = value.msg or "" + + # Cause and context must be set later to prevent recursion errors + self.cause = None + self.context = None + + def format_exception(self): + """Iterates over the formatted lines for this exception report. + + The yielded values are strings, each ending in a newline and some + containing internal newlines. When these lines are concatenated and + printed, exactly the same text is printed as does print_exception(). + """ + seen = set() + + def _format_recursive(exc): + seen.add(exc) + if exc.cause and exc.cause not in seen: + yield from _format_recursive(exc.cause) + yield _cause_message + elif exc.context and exc.context not in seen: + yield from _format_recursive(exc.context) + yield _context_message + + stype = exc.exc_type + if exc.exc_type is not None: + stype = exc.exc_type.__name__ + smod = exc.exc_type.__module__ + if smod not in ("__main__", "builtins"): + stype = smod + '.' + stype + + if exc.exc_traceback: + yield _traceback_message + yield from _format_traceback(exc.exc_traceback) + if exc.exc_offset is not None: + badline = exc.exc_traceback[-1][3] + caretspace = badline.rstrip('\n')[:exc.exc_offset].lstrip() + # non-space whitespace (likes tabs) must be kept + caretspace = ((c.isspace() and c or ' ') + for c in caretspace) + # only three spaces to account for offset1 == pos 0 + yield ' {}^\n'.format(''.join(caretspace)) + if not exc.exc_str: + yield "%s\n" % stype + else: + yield "%s: %s\n" % (stype, exc.exc_str) + + yield from _format_recursive(self) + + def print_exception(self, file=None): + """Print the formatted exception to the given file.""" + if file is None: + file = sys.stderr + for line in self.format_exception(): + print(line, file=file, end='') + + +def _exc_chain(exc, custom_tb=None, seen=None): + if seen is None: + seen = set() + seen.add(exc) + context = exc.__context__ + cause = exc.__cause__ + if cause is not None and cause not in seen: + yield from _exc_chain(cause, None, seen) + if context is not None and context not in seen: + yield from _exc_chain(context, None, seen) + yield (exc, custom_tb or exc.__traceback__) + + +def extract_exception(etype, value, tb=None, limit=None, chain=True, + defer=False): + """Extract exception and traceback information and return a summary. + + The summary object contains the exception type, string representation and a + list of tuples as returned by extract_tb(). If 'chain' is true (the + default), the cause and context attributes will be set to a summary object + describing the cause and context exceptions. If 'defer' is true, the line + text will not be extracted until the exception is formatted. + """ + if chain and value is not None: + exceptions = _exc_chain(value, tb) + else: + exceptions = [(value, tb)] + + summaries = {exc: ExceptionSummary(exc, tb, limit=limit, defer=defer) + for (exc, tb) in exceptions} + for exc, summary in summaries.items(): + if exc is not None: + summary.cause = summaries.get(exc.__cause__) + if exc.__cause__ is not None or not exc.__suppress_context__: + summary.context = summaries.get(exc.__context__) + return summaries[value] + # # Printing and Extracting Stacks. # -def _extract_stack_iter(f, limit=None): - return _extract_tb_or_stack_iter( - f, limit, lambda f: (f, f.f_lineno, f.f_back)) +def _iter_stack(f): + while f is not None: + yield f, f.f_lineno + f = f.f_back + def _get_stack(f): if f is None: f = sys._getframe().f_back.f_back return f + def print_stack(f=None, limit=None, file=None): """Print a stack trace from its invocation point. @@ -283,10 +460,12 @@ """ print_list(extract_stack(_get_stack(f), limit=limit), file=file) + def format_stack(f=None, limit=None): """Shorthand for 'format_list(extract_stack(f, limit))'.""" return format_list(extract_stack(_get_stack(f), limit=limit)) + def extract_stack(f=None, limit=None): """Extract the raw traceback from the current stack frame. @@ -296,6 +475,6 @@ line number, function name, text), and the entries are in order from oldest to newest stack frame. """ - stack = list(_extract_stack_iter(_get_stack(f), limit=limit)) + stack = list(_extract_tb_tuples(_iter_stack(_get_stack(f)), limit=limit)) stack.reverse() return stack