(root)/
Python-3.12.0/
Lib/
traceback.py
       1  """Extract, format and print information about Python stack traces."""
       2  
       3  import collections.abc
       4  import itertools
       5  import linecache
       6  import sys
       7  import textwrap
       8  from contextlib import suppress
       9  
      10  __all__ = ['extract_stack', 'extract_tb', 'format_exception',
      11             'format_exception_only', 'format_list', 'format_stack',
      12             'format_tb', 'print_exc', 'format_exc', 'print_exception',
      13             'print_last', 'print_stack', 'print_tb', 'clear_frames',
      14             'FrameSummary', 'StackSummary', 'TracebackException',
      15             'walk_stack', 'walk_tb']
      16  
      17  #
      18  # Formatting and printing lists of traceback lines.
      19  #
      20  
      21  def print_list(extracted_list, file=None):
      22      """Print the list of tuples as returned by extract_tb() or
      23      extract_stack() as a formatted stack trace to the given file."""
      24      if file is None:
      25          file = sys.stderr
      26      for item in StackSummary.from_list(extracted_list).format():
      27          print(item, file=file, end="")
      28  
      29  def format_list(extracted_list):
      30      """Format a list of tuples or FrameSummary objects for printing.
      31  
      32      Given a list of tuples or FrameSummary objects as returned by
      33      extract_tb() or extract_stack(), return a list of strings ready
      34      for printing.
      35  
      36      Each string in the resulting list corresponds to the item with the
      37      same index in the argument list.  Each string ends in a newline;
      38      the strings may contain internal newlines as well, for those items
      39      whose source text line is not None.
      40      """
      41      return StackSummary.from_list(extracted_list).format()
      42  
      43  #
      44  # Printing and Extracting Tracebacks.
      45  #
      46  
      47  def print_tb(tb, limit=None, file=None):
      48      """Print up to 'limit' stack trace entries from the traceback 'tb'.
      49  
      50      If 'limit' is omitted or None, all entries are printed.  If 'file'
      51      is omitted or None, the output goes to sys.stderr; otherwise
      52      'file' should be an open file or file-like object with a write()
      53      method.
      54      """
      55      print_list(extract_tb(tb, limit=limit), file=file)
      56  
      57  def format_tb(tb, limit=None):
      58      """A shorthand for 'format_list(extract_tb(tb, limit))'."""
      59      return extract_tb(tb, limit=limit).format()
      60  
      61  def extract_tb(tb, limit=None):
      62      """
      63      Return a StackSummary object representing a list of
      64      pre-processed entries from traceback.
      65  
      66      This is useful for alternate formatting of stack traces.  If
      67      'limit' is omitted or None, all entries are extracted.  A
      68      pre-processed stack trace entry is a FrameSummary object
      69      containing attributes filename, lineno, name, and line
      70      representing the information that is usually printed for a stack
      71      trace.  The line is a string with leading and trailing
      72      whitespace stripped; if the source is not available it is None.
      73      """
      74      return StackSummary._extract_from_extended_frame_gen(
      75          _walk_tb_with_full_positions(tb), limit=limit)
      76  
      77  #
      78  # Exception formatting and output.
      79  #
      80  
      81  _cause_message = (
      82      "\nThe above exception was the direct cause "
      83      "of the following exception:\n\n")
      84  
      85  _context_message = (
      86      "\nDuring handling of the above exception, "
      87      "another exception occurred:\n\n")
      88  
      89  
      90  class ESC[4;38;5;81m_Sentinel:
      91      def __repr__(self):
      92          return "<implicit>"
      93  
      94  _sentinel = _Sentinel()
      95  
      96  def _parse_value_tb(exc, value, tb):
      97      if (value is _sentinel) != (tb is _sentinel):
      98          raise ValueError("Both or neither of value and tb must be given")
      99      if value is tb is _sentinel:
     100          if exc is not None:
     101              if isinstance(exc, BaseException):
     102                  return exc, exc.__traceback__
     103  
     104              raise TypeError(f'Exception expected for value, '
     105                              f'{type(exc).__name__} found')
     106          else:
     107              return None, None
     108      return value, tb
     109  
     110  
     111  def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
     112                      file=None, chain=True):
     113      """Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
     114  
     115      This differs from print_tb() in the following ways: (1) if
     116      traceback is not None, it prints a header "Traceback (most recent
     117      call last):"; (2) it prints the exception type and value after the
     118      stack trace; (3) if type is SyntaxError and value has the
     119      appropriate format, it prints the line where the syntax error
     120      occurred with a caret on the next line indicating the approximate
     121      position of the error.
     122      """
     123      value, tb = _parse_value_tb(exc, value, tb)
     124      te = TracebackException(type(value), value, tb, limit=limit, compact=True)
     125      te.print(file=file, chain=chain)
     126  
     127  
     128  def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
     129                       chain=True):
     130      """Format a stack trace and the exception information.
     131  
     132      The arguments have the same meaning as the corresponding arguments
     133      to print_exception().  The return value is a list of strings, each
     134      ending in a newline and some containing internal newlines.  When
     135      these lines are concatenated and printed, exactly the same text is
     136      printed as does print_exception().
     137      """
     138      value, tb = _parse_value_tb(exc, value, tb)
     139      te = TracebackException(type(value), value, tb, limit=limit, compact=True)
     140      return list(te.format(chain=chain))
     141  
     142  
     143  def format_exception_only(exc, /, value=_sentinel):
     144      """Format the exception part of a traceback.
     145  
     146      The return value is a list of strings, each ending in a newline.
     147  
     148      Normally, the list contains a single string; however, for
     149      SyntaxError exceptions, it contains several lines that (when
     150      printed) display detailed information about where the syntax
     151      error occurred.
     152  
     153      The message indicating which exception occurred is always the last
     154      string in the list.
     155  
     156      """
     157      if value is _sentinel:
     158          value = exc
     159      te = TracebackException(type(value), value, None, compact=True)
     160      return list(te.format_exception_only())
     161  
     162  
     163  # -- not official API but folk probably use these two functions.
     164  
     165  def _format_final_exc_line(etype, value):
     166      valuestr = _safe_string(value, 'exception')
     167      if value is None or not valuestr:
     168          line = "%s\n" % etype
     169      else:
     170          line = "%s: %s\n" % (etype, valuestr)
     171      return line
     172  
     173  def _safe_string(value, what, func=str):
     174      try:
     175          return func(value)
     176      except:
     177          return f'<{what} {func.__name__}() failed>'
     178  
     179  # --
     180  
     181  def print_exc(limit=None, file=None, chain=True):
     182      """Shorthand for 'print_exception(sys.exception(), limit, file, chain)'."""
     183      print_exception(sys.exception(), limit=limit, file=file, chain=chain)
     184  
     185  def format_exc(limit=None, chain=True):
     186      """Like print_exc() but return a string."""
     187      return "".join(format_exception(sys.exception(), limit=limit, chain=chain))
     188  
     189  def print_last(limit=None, file=None, chain=True):
     190      """This is a shorthand for 'print_exception(sys.last_exc, limit, file, chain)'."""
     191      if not hasattr(sys, "last_exc") and not hasattr(sys, "last_type"):
     192          raise ValueError("no last exception")
     193  
     194      if hasattr(sys, "last_exc"):
     195          print_exception(sys.last_exc, limit, file, chain)
     196      else:
     197          print_exception(sys.last_type, sys.last_value, sys.last_traceback,
     198                          limit, file, chain)
     199  
     200  
     201  #
     202  # Printing and Extracting Stacks.
     203  #
     204  
     205  def print_stack(f=None, limit=None, file=None):
     206      """Print a stack trace from its invocation point.
     207  
     208      The optional 'f' argument can be used to specify an alternate
     209      stack frame at which to start. The optional 'limit' and 'file'
     210      arguments have the same meaning as for print_exception().
     211      """
     212      if f is None:
     213          f = sys._getframe().f_back
     214      print_list(extract_stack(f, limit=limit), file=file)
     215  
     216  
     217  def format_stack(f=None, limit=None):
     218      """Shorthand for 'format_list(extract_stack(f, limit))'."""
     219      if f is None:
     220          f = sys._getframe().f_back
     221      return format_list(extract_stack(f, limit=limit))
     222  
     223  
     224  def extract_stack(f=None, limit=None):
     225      """Extract the raw traceback from the current stack frame.
     226  
     227      The return value has the same format as for extract_tb().  The
     228      optional 'f' and 'limit' arguments have the same meaning as for
     229      print_stack().  Each item in the list is a quadruple (filename,
     230      line number, function name, text), and the entries are in order
     231      from oldest to newest stack frame.
     232      """
     233      if f is None:
     234          f = sys._getframe().f_back
     235      stack = StackSummary.extract(walk_stack(f), limit=limit)
     236      stack.reverse()
     237      return stack
     238  
     239  
     240  def clear_frames(tb):
     241      "Clear all references to local variables in the frames of a traceback."
     242      while tb is not None:
     243          try:
     244              tb.tb_frame.clear()
     245          except RuntimeError:
     246              # Ignore the exception raised if the frame is still executing.
     247              pass
     248          tb = tb.tb_next
     249  
     250  
     251  class ESC[4;38;5;81mFrameSummary:
     252      """Information about a single frame from a traceback.
     253  
     254      - :attr:`filename` The filename for the frame.
     255      - :attr:`lineno` The line within filename for the frame that was
     256        active when the frame was captured.
     257      - :attr:`name` The name of the function or method that was executing
     258        when the frame was captured.
     259      - :attr:`line` The text from the linecache module for the
     260        of code that was running when the frame was captured.
     261      - :attr:`locals` Either None if locals were not supplied, or a dict
     262        mapping the name to the repr() of the variable.
     263      """
     264  
     265      __slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno',
     266                   'name', '_line', 'locals')
     267  
     268      def __init__(self, filename, lineno, name, *, lookup_line=True,
     269              locals=None, line=None,
     270              end_lineno=None, colno=None, end_colno=None):
     271          """Construct a FrameSummary.
     272  
     273          :param lookup_line: If True, `linecache` is consulted for the source
     274              code line. Otherwise, the line will be looked up when first needed.
     275          :param locals: If supplied the frame locals, which will be captured as
     276              object representations.
     277          :param line: If provided, use this instead of looking up the line in
     278              the linecache.
     279          """
     280          self.filename = filename
     281          self.lineno = lineno
     282          self.name = name
     283          self._line = line
     284          if lookup_line:
     285              self.line
     286          self.locals = {k: _safe_string(v, 'local', func=repr)
     287              for k, v in locals.items()} if locals else None
     288          self.end_lineno = end_lineno
     289          self.colno = colno
     290          self.end_colno = end_colno
     291  
     292      def __eq__(self, other):
     293          if isinstance(other, FrameSummary):
     294              return (self.filename == other.filename and
     295                      self.lineno == other.lineno and
     296                      self.name == other.name and
     297                      self.locals == other.locals)
     298          if isinstance(other, tuple):
     299              return (self.filename, self.lineno, self.name, self.line) == other
     300          return NotImplemented
     301  
     302      def __getitem__(self, pos):
     303          return (self.filename, self.lineno, self.name, self.line)[pos]
     304  
     305      def __iter__(self):
     306          return iter([self.filename, self.lineno, self.name, self.line])
     307  
     308      def __repr__(self):
     309          return "<FrameSummary file {filename}, line {lineno} in {name}>".format(
     310              filename=self.filename, lineno=self.lineno, name=self.name)
     311  
     312      def __len__(self):
     313          return 4
     314  
     315      @property
     316      def _original_line(self):
     317          # Returns the line as-is from the source, without modifying whitespace.
     318          self.line
     319          return self._line
     320  
     321      @property
     322      def line(self):
     323          if self._line is None:
     324              if self.lineno is None:
     325                  return None
     326              self._line = linecache.getline(self.filename, self.lineno)
     327          return self._line.strip()
     328  
     329  
     330  def walk_stack(f):
     331      """Walk a stack yielding the frame and line number for each frame.
     332  
     333      This will follow f.f_back from the given frame. If no frame is given, the
     334      current stack is used. Usually used with StackSummary.extract.
     335      """
     336      if f is None:
     337          f = sys._getframe().f_back.f_back.f_back.f_back
     338      while f is not None:
     339          yield f, f.f_lineno
     340          f = f.f_back
     341  
     342  
     343  def walk_tb(tb):
     344      """Walk a traceback yielding the frame and line number for each frame.
     345  
     346      This will follow tb.tb_next (and thus is in the opposite order to
     347      walk_stack). Usually used with StackSummary.extract.
     348      """
     349      while tb is not None:
     350          yield tb.tb_frame, tb.tb_lineno
     351          tb = tb.tb_next
     352  
     353  
     354  def _walk_tb_with_full_positions(tb):
     355      # Internal version of walk_tb that yields full code positions including
     356      # end line and column information.
     357      while tb is not None:
     358          positions = _get_code_position(tb.tb_frame.f_code, tb.tb_lasti)
     359          # Yield tb_lineno when co_positions does not have a line number to
     360          # maintain behavior with walk_tb.
     361          if positions[0] is None:
     362              yield tb.tb_frame, (tb.tb_lineno, ) + positions[1:]
     363          else:
     364              yield tb.tb_frame, positions
     365          tb = tb.tb_next
     366  
     367  
     368  def _get_code_position(code, instruction_index):
     369      if instruction_index < 0:
     370          return (None, None, None, None)
     371      positions_gen = code.co_positions()
     372      return next(itertools.islice(positions_gen, instruction_index // 2, None))
     373  
     374  
     375  _RECURSIVE_CUTOFF = 3 # Also hardcoded in traceback.c.
     376  
     377  class ESC[4;38;5;81mStackSummary(ESC[4;38;5;149mlist):
     378      """A list of FrameSummary objects, representing a stack of frames."""
     379  
     380      @classmethod
     381      def extract(klass, frame_gen, *, limit=None, lookup_lines=True,
     382              capture_locals=False):
     383          """Create a StackSummary from a traceback or stack object.
     384  
     385          :param frame_gen: A generator that yields (frame, lineno) tuples
     386              whose summaries are to be included in the stack.
     387          :param limit: None to include all frames or the number of frames to
     388              include.
     389          :param lookup_lines: If True, lookup lines for each frame immediately,
     390              otherwise lookup is deferred until the frame is rendered.
     391          :param capture_locals: If True, the local variables from each frame will
     392              be captured as object representations into the FrameSummary.
     393          """
     394          def extended_frame_gen():
     395              for f, lineno in frame_gen:
     396                  yield f, (lineno, None, None, None)
     397  
     398          return klass._extract_from_extended_frame_gen(
     399              extended_frame_gen(), limit=limit, lookup_lines=lookup_lines,
     400              capture_locals=capture_locals)
     401  
     402      @classmethod
     403      def _extract_from_extended_frame_gen(klass, frame_gen, *, limit=None,
     404              lookup_lines=True, capture_locals=False):
     405          # Same as extract but operates on a frame generator that yields
     406          # (frame, (lineno, end_lineno, colno, end_colno)) in the stack.
     407          # Only lineno is required, the remaining fields can be None if the
     408          # information is not available.
     409          if limit is None:
     410              limit = getattr(sys, 'tracebacklimit', None)
     411              if limit is not None and limit < 0:
     412                  limit = 0
     413          if limit is not None:
     414              if limit >= 0:
     415                  frame_gen = itertools.islice(frame_gen, limit)
     416              else:
     417                  frame_gen = collections.deque(frame_gen, maxlen=-limit)
     418  
     419          result = klass()
     420          fnames = set()
     421          for f, (lineno, end_lineno, colno, end_colno) in frame_gen:
     422              co = f.f_code
     423              filename = co.co_filename
     424              name = co.co_name
     425  
     426              fnames.add(filename)
     427              linecache.lazycache(filename, f.f_globals)
     428              # Must defer line lookups until we have called checkcache.
     429              if capture_locals:
     430                  f_locals = f.f_locals
     431              else:
     432                  f_locals = None
     433              result.append(FrameSummary(
     434                  filename, lineno, name, lookup_line=False, locals=f_locals,
     435                  end_lineno=end_lineno, colno=colno, end_colno=end_colno))
     436          for filename in fnames:
     437              linecache.checkcache(filename)
     438          # If immediate lookup was desired, trigger lookups now.
     439          if lookup_lines:
     440              for f in result:
     441                  f.line
     442          return result
     443  
     444      @classmethod
     445      def from_list(klass, a_list):
     446          """
     447          Create a StackSummary object from a supplied list of
     448          FrameSummary objects or old-style list of tuples.
     449          """
     450          # While doing a fast-path check for isinstance(a_list, StackSummary) is
     451          # appealing, idlelib.run.cleanup_traceback and other similar code may
     452          # break this by making arbitrary frames plain tuples, so we need to
     453          # check on a frame by frame basis.
     454          result = StackSummary()
     455          for frame in a_list:
     456              if isinstance(frame, FrameSummary):
     457                  result.append(frame)
     458              else:
     459                  filename, lineno, name, line = frame
     460                  result.append(FrameSummary(filename, lineno, name, line=line))
     461          return result
     462  
     463      def format_frame_summary(self, frame_summary):
     464          """Format the lines for a single FrameSummary.
     465  
     466          Returns a string representing one frame involved in the stack. This
     467          gets called for every frame to be printed in the stack summary.
     468          """
     469          row = []
     470          row.append('  File "{}", line {}, in {}\n'.format(
     471              frame_summary.filename, frame_summary.lineno, frame_summary.name))
     472          if frame_summary.line:
     473              stripped_line = frame_summary.line.strip()
     474              row.append('    {}\n'.format(stripped_line))
     475  
     476              orig_line_len = len(frame_summary._original_line)
     477              frame_line_len = len(frame_summary.line.lstrip())
     478              stripped_characters = orig_line_len - frame_line_len
     479              if (
     480                  frame_summary.colno is not None
     481                  and frame_summary.end_colno is not None
     482              ):
     483                  start_offset = _byte_offset_to_character_offset(
     484                      frame_summary._original_line, frame_summary.colno) + 1
     485                  end_offset = _byte_offset_to_character_offset(
     486                      frame_summary._original_line, frame_summary.end_colno) + 1
     487  
     488                  anchors = None
     489                  if frame_summary.lineno == frame_summary.end_lineno:
     490                      with suppress(Exception):
     491                          anchors = _extract_caret_anchors_from_line_segment(
     492                              frame_summary._original_line[start_offset - 1:end_offset - 1]
     493                          )
     494                  else:
     495                      end_offset = stripped_characters + len(stripped_line)
     496  
     497                  # show indicators if primary char doesn't span the frame line
     498                  if end_offset - start_offset < len(stripped_line) or (
     499                          anchors and anchors.right_start_offset - anchors.left_end_offset > 0):
     500                      row.append('    ')
     501                      row.append(' ' * (start_offset - stripped_characters))
     502  
     503                      if anchors:
     504                          row.append(anchors.primary_char * (anchors.left_end_offset))
     505                          row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
     506                          row.append(anchors.primary_char * (end_offset - start_offset - anchors.right_start_offset))
     507                      else:
     508                          row.append('^' * (end_offset - start_offset))
     509  
     510                      row.append('\n')
     511  
     512          if frame_summary.locals:
     513              for name, value in sorted(frame_summary.locals.items()):
     514                  row.append('    {name} = {value}\n'.format(name=name, value=value))
     515  
     516          return ''.join(row)
     517  
     518      def format(self):
     519          """Format the stack ready for printing.
     520  
     521          Returns a list of strings ready for printing.  Each string in the
     522          resulting list corresponds to a single frame from the stack.
     523          Each string ends in a newline; the strings may contain internal
     524          newlines as well, for those items with source text lines.
     525  
     526          For long sequences of the same frame and line, the first few
     527          repetitions are shown, followed by a summary line stating the exact
     528          number of further repetitions.
     529          """
     530          result = []
     531          last_file = None
     532          last_line = None
     533          last_name = None
     534          count = 0
     535          for frame_summary in self:
     536              formatted_frame = self.format_frame_summary(frame_summary)
     537              if formatted_frame is None:
     538                  continue
     539              if (last_file is None or last_file != frame_summary.filename or
     540                  last_line is None or last_line != frame_summary.lineno or
     541                  last_name is None or last_name != frame_summary.name):
     542                  if count > _RECURSIVE_CUTOFF:
     543                      count -= _RECURSIVE_CUTOFF
     544                      result.append(
     545                          f'  [Previous line repeated {count} more '
     546                          f'time{"s" if count > 1 else ""}]\n'
     547                      )
     548                  last_file = frame_summary.filename
     549                  last_line = frame_summary.lineno
     550                  last_name = frame_summary.name
     551                  count = 0
     552              count += 1
     553              if count > _RECURSIVE_CUTOFF:
     554                  continue
     555              result.append(formatted_frame)
     556  
     557          if count > _RECURSIVE_CUTOFF:
     558              count -= _RECURSIVE_CUTOFF
     559              result.append(
     560                  f'  [Previous line repeated {count} more '
     561                  f'time{"s" if count > 1 else ""}]\n'
     562              )
     563          return result
     564  
     565  
     566  def _byte_offset_to_character_offset(str, offset):
     567      as_utf8 = str.encode('utf-8')
     568      return len(as_utf8[:offset].decode("utf-8", errors="replace"))
     569  
     570  
     571  _Anchors = collections.namedtuple(
     572      "_Anchors",
     573      [
     574          "left_end_offset",
     575          "right_start_offset",
     576          "primary_char",
     577          "secondary_char",
     578      ],
     579      defaults=["~", "^"]
     580  )
     581  
     582  def _extract_caret_anchors_from_line_segment(segment):
     583      import ast
     584  
     585      try:
     586          tree = ast.parse(segment)
     587      except SyntaxError:
     588          return None
     589  
     590      if len(tree.body) != 1:
     591          return None
     592  
     593      normalize = lambda offset: _byte_offset_to_character_offset(segment, offset)
     594      statement = tree.body[0]
     595      match statement:
     596          case ast.Expr(expr):
     597              match expr:
     598                  case ast.BinOp():
     599                      operator_start = normalize(expr.left.end_col_offset)
     600                      operator_end = normalize(expr.right.col_offset)
     601                      operator_str = segment[operator_start:operator_end]
     602                      operator_offset = len(operator_str) - len(operator_str.lstrip())
     603  
     604                      left_anchor = expr.left.end_col_offset + operator_offset
     605                      right_anchor = left_anchor + 1
     606                      if (
     607                          operator_offset + 1 < len(operator_str)
     608                          and not operator_str[operator_offset + 1].isspace()
     609                      ):
     610                          right_anchor += 1
     611  
     612                      while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch in ")#"):
     613                          left_anchor += 1
     614                          right_anchor += 1
     615                      return _Anchors(normalize(left_anchor), normalize(right_anchor))
     616                  case ast.Subscript():
     617                      left_anchor = normalize(expr.value.end_col_offset)
     618                      right_anchor = normalize(expr.slice.end_col_offset + 1)
     619                      while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch != "["):
     620                          left_anchor += 1
     621                      while right_anchor < len(segment) and ((ch := segment[right_anchor]).isspace() or ch != "]"):
     622                          right_anchor += 1
     623                      if right_anchor < len(segment):
     624                          right_anchor += 1
     625                      return _Anchors(left_anchor, right_anchor)
     626  
     627      return None
     628  
     629  
     630  class ESC[4;38;5;81m_ExceptionPrintContext:
     631      def __init__(self):
     632          self.seen = set()
     633          self.exception_group_depth = 0
     634          self.need_close = False
     635  
     636      def indent(self):
     637          return ' ' * (2 * self.exception_group_depth)
     638  
     639      def emit(self, text_gen, margin_char=None):
     640          if margin_char is None:
     641              margin_char = '|'
     642          indent_str = self.indent()
     643          if self.exception_group_depth:
     644              indent_str += margin_char + ' '
     645  
     646          if isinstance(text_gen, str):
     647              yield textwrap.indent(text_gen, indent_str, lambda line: True)
     648          else:
     649              for text in text_gen:
     650                  yield textwrap.indent(text, indent_str, lambda line: True)
     651  
     652  
     653  class ESC[4;38;5;81mTracebackException:
     654      """An exception ready for rendering.
     655  
     656      The traceback module captures enough attributes from the original exception
     657      to this intermediary form to ensure that no references are held, while
     658      still being able to fully print or format it.
     659  
     660      max_group_width and max_group_depth control the formatting of exception
     661      groups. The depth refers to the nesting level of the group, and the width
     662      refers to the size of a single exception group's exceptions array. The
     663      formatted output is truncated when either limit is exceeded.
     664  
     665      Use `from_exception` to create TracebackException instances from exception
     666      objects, or the constructor to create TracebackException instances from
     667      individual components.
     668  
     669      - :attr:`__cause__` A TracebackException of the original *__cause__*.
     670      - :attr:`__context__` A TracebackException of the original *__context__*.
     671      - :attr:`exceptions` For exception groups - a list of TracebackException
     672        instances for the nested *exceptions*.  ``None`` for other exceptions.
     673      - :attr:`__suppress_context__` The *__suppress_context__* value from the
     674        original exception.
     675      - :attr:`stack` A `StackSummary` representing the traceback.
     676      - :attr:`exc_type` The class of the original traceback.
     677      - :attr:`filename` For syntax errors - the filename where the error
     678        occurred.
     679      - :attr:`lineno` For syntax errors - the linenumber where the error
     680        occurred.
     681      - :attr:`end_lineno` For syntax errors - the end linenumber where the error
     682        occurred. Can be `None` if not present.
     683      - :attr:`text` For syntax errors - the text where the error
     684        occurred.
     685      - :attr:`offset` For syntax errors - the offset into the text where the
     686        error occurred.
     687      - :attr:`end_offset` For syntax errors - the end offset into the text where
     688        the error occurred. Can be `None` if not present.
     689      - :attr:`msg` For syntax errors - the compiler error message.
     690      """
     691  
     692      def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
     693              lookup_lines=True, capture_locals=False, compact=False,
     694              max_group_width=15, max_group_depth=10, _seen=None):
     695          # NB: we need to accept exc_traceback, exc_value, exc_traceback to
     696          # permit backwards compat with the existing API, otherwise we
     697          # need stub thunk objects just to glue it together.
     698          # Handle loops in __cause__ or __context__.
     699          is_recursive_call = _seen is not None
     700          if _seen is None:
     701              _seen = set()
     702          _seen.add(id(exc_value))
     703  
     704          self.max_group_width = max_group_width
     705          self.max_group_depth = max_group_depth
     706  
     707          self.stack = StackSummary._extract_from_extended_frame_gen(
     708              _walk_tb_with_full_positions(exc_traceback),
     709              limit=limit, lookup_lines=lookup_lines,
     710              capture_locals=capture_locals)
     711          self.exc_type = exc_type
     712          # Capture now to permit freeing resources: only complication is in the
     713          # unofficial API _format_final_exc_line
     714          self._str = _safe_string(exc_value, 'exception')
     715          self.__notes__ = getattr(exc_value, '__notes__', None)
     716  
     717          if exc_type and issubclass(exc_type, SyntaxError):
     718              # Handle SyntaxError's specially
     719              self.filename = exc_value.filename
     720              lno = exc_value.lineno
     721              self.lineno = str(lno) if lno is not None else None
     722              end_lno = exc_value.end_lineno
     723              self.end_lineno = str(end_lno) if end_lno is not None else None
     724              self.text = exc_value.text
     725              self.offset = exc_value.offset
     726              self.end_offset = exc_value.end_offset
     727              self.msg = exc_value.msg
     728          elif exc_type and issubclass(exc_type, ImportError) and \
     729                  getattr(exc_value, "name_from", None) is not None:
     730              wrong_name = getattr(exc_value, "name_from", None)
     731              suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
     732              if suggestion:
     733                  self._str += f". Did you mean: '{suggestion}'?"
     734          elif exc_type and issubclass(exc_type, (NameError, AttributeError)) and \
     735                  getattr(exc_value, "name", None) is not None:
     736              wrong_name = getattr(exc_value, "name", None)
     737              suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
     738              if suggestion:
     739                  self._str += f". Did you mean: '{suggestion}'?"
     740              if issubclass(exc_type, NameError):
     741                  wrong_name = getattr(exc_value, "name", None)
     742                  if wrong_name is not None and wrong_name in sys.stdlib_module_names:
     743                      if suggestion:
     744                          self._str += f" Or did you forget to import '{wrong_name}'"
     745                      else:
     746                          self._str += f". Did you forget to import '{wrong_name}'"
     747          if lookup_lines:
     748              self._load_lines()
     749          self.__suppress_context__ = \
     750              exc_value.__suppress_context__ if exc_value is not None else False
     751  
     752          # Convert __cause__ and __context__ to `TracebackExceptions`s, use a
     753          # queue to avoid recursion (only the top-level call gets _seen == None)
     754          if not is_recursive_call:
     755              queue = [(self, exc_value)]
     756              while queue:
     757                  te, e = queue.pop()
     758                  if (e and e.__cause__ is not None
     759                      and id(e.__cause__) not in _seen):
     760                      cause = TracebackException(
     761                          type(e.__cause__),
     762                          e.__cause__,
     763                          e.__cause__.__traceback__,
     764                          limit=limit,
     765                          lookup_lines=lookup_lines,
     766                          capture_locals=capture_locals,
     767                          max_group_width=max_group_width,
     768                          max_group_depth=max_group_depth,
     769                          _seen=_seen)
     770                  else:
     771                      cause = None
     772  
     773                  if compact:
     774                      need_context = (cause is None and
     775                                      e is not None and
     776                                      not e.__suppress_context__)
     777                  else:
     778                      need_context = True
     779                  if (e and e.__context__ is not None
     780                      and need_context and id(e.__context__) not in _seen):
     781                      context = TracebackException(
     782                          type(e.__context__),
     783                          e.__context__,
     784                          e.__context__.__traceback__,
     785                          limit=limit,
     786                          lookup_lines=lookup_lines,
     787                          capture_locals=capture_locals,
     788                          max_group_width=max_group_width,
     789                          max_group_depth=max_group_depth,
     790                          _seen=_seen)
     791                  else:
     792                      context = None
     793  
     794                  if e and isinstance(e, BaseExceptionGroup):
     795                      exceptions = []
     796                      for exc in e.exceptions:
     797                          texc = TracebackException(
     798                              type(exc),
     799                              exc,
     800                              exc.__traceback__,
     801                              limit=limit,
     802                              lookup_lines=lookup_lines,
     803                              capture_locals=capture_locals,
     804                              max_group_width=max_group_width,
     805                              max_group_depth=max_group_depth,
     806                              _seen=_seen)
     807                          exceptions.append(texc)
     808                  else:
     809                      exceptions = None
     810  
     811                  te.__cause__ = cause
     812                  te.__context__ = context
     813                  te.exceptions = exceptions
     814                  if cause:
     815                      queue.append((te.__cause__, e.__cause__))
     816                  if context:
     817                      queue.append((te.__context__, e.__context__))
     818                  if exceptions:
     819                      queue.extend(zip(te.exceptions, e.exceptions))
     820  
     821      @classmethod
     822      def from_exception(cls, exc, *args, **kwargs):
     823          """Create a TracebackException from an exception."""
     824          return cls(type(exc), exc, exc.__traceback__, *args, **kwargs)
     825  
     826      def _load_lines(self):
     827          """Private API. force all lines in the stack to be loaded."""
     828          for frame in self.stack:
     829              frame.line
     830  
     831      def __eq__(self, other):
     832          if isinstance(other, TracebackException):
     833              return self.__dict__ == other.__dict__
     834          return NotImplemented
     835  
     836      def __str__(self):
     837          return self._str
     838  
     839      def format_exception_only(self):
     840          """Format the exception part of the traceback.
     841  
     842          The return value is a generator of strings, each ending in a newline.
     843  
     844          Normally, the generator emits a single string; however, for
     845          SyntaxError exceptions, it emits several lines that (when
     846          printed) display detailed information about where the syntax
     847          error occurred.
     848  
     849          The message indicating which exception occurred is always the last
     850          string in the output.
     851          """
     852          if self.exc_type is None:
     853              yield _format_final_exc_line(None, self._str)
     854              return
     855  
     856          stype = self.exc_type.__qualname__
     857          smod = self.exc_type.__module__
     858          if smod not in ("__main__", "builtins"):
     859              if not isinstance(smod, str):
     860                  smod = "<unknown>"
     861              stype = smod + '.' + stype
     862  
     863          if not issubclass(self.exc_type, SyntaxError):
     864              yield _format_final_exc_line(stype, self._str)
     865          else:
     866              yield from self._format_syntax_error(stype)
     867  
     868          if (
     869              isinstance(self.__notes__, collections.abc.Sequence)
     870              and not isinstance(self.__notes__, (str, bytes))
     871          ):
     872              for note in self.__notes__:
     873                  note = _safe_string(note, 'note')
     874                  yield from [l + '\n' for l in note.split('\n')]
     875          elif self.__notes__ is not None:
     876              yield "{}\n".format(_safe_string(self.__notes__, '__notes__', func=repr))
     877  
     878      def _format_syntax_error(self, stype):
     879          """Format SyntaxError exceptions (internal helper)."""
     880          # Show exactly where the problem was found.
     881          filename_suffix = ''
     882          if self.lineno is not None:
     883              yield '  File "{}", line {}\n'.format(
     884                  self.filename or "<string>", self.lineno)
     885          elif self.filename is not None:
     886              filename_suffix = ' ({})'.format(self.filename)
     887  
     888          text = self.text
     889          if text is not None:
     890              # text  = "   foo\n"
     891              # rtext = "   foo"
     892              # ltext =    "foo"
     893              rtext = text.rstrip('\n')
     894              ltext = rtext.lstrip(' \n\f')
     895              spaces = len(rtext) - len(ltext)
     896              yield '    {}\n'.format(ltext)
     897  
     898              if self.offset is not None:
     899                  offset = self.offset
     900                  end_offset = self.end_offset if self.end_offset not in {None, 0} else offset
     901                  if offset == end_offset or end_offset == -1:
     902                      end_offset = offset + 1
     903  
     904                  # Convert 1-based column offset to 0-based index into stripped text
     905                  colno = offset - 1 - spaces
     906                  end_colno = end_offset - 1 - spaces
     907                  if colno >= 0:
     908                      # non-space whitespace (likes tabs) must be kept for alignment
     909                      caretspace = ((c if c.isspace() else ' ') for c in ltext[:colno])
     910                      yield '    {}{}'.format("".join(caretspace), ('^' * (end_colno - colno) + "\n"))
     911          msg = self.msg or "<no detail available>"
     912          yield "{}: {}{}\n".format(stype, msg, filename_suffix)
     913  
     914      def format(self, *, chain=True, _ctx=None):
     915          """Format the exception.
     916  
     917          If chain is not *True*, *__cause__* and *__context__* will not be formatted.
     918  
     919          The return value is a generator of strings, each ending in a newline and
     920          some containing internal newlines. `print_exception` is a wrapper around
     921          this method which just prints the lines to a file.
     922  
     923          The message indicating which exception occurred is always the last
     924          string in the output.
     925          """
     926  
     927          if _ctx is None:
     928              _ctx = _ExceptionPrintContext()
     929  
     930          output = []
     931          exc = self
     932          if chain:
     933              while exc:
     934                  if exc.__cause__ is not None:
     935                      chained_msg = _cause_message
     936                      chained_exc = exc.__cause__
     937                  elif (exc.__context__  is not None and
     938                        not exc.__suppress_context__):
     939                      chained_msg = _context_message
     940                      chained_exc = exc.__context__
     941                  else:
     942                      chained_msg = None
     943                      chained_exc = None
     944  
     945                  output.append((chained_msg, exc))
     946                  exc = chained_exc
     947          else:
     948              output.append((None, exc))
     949  
     950          for msg, exc in reversed(output):
     951              if msg is not None:
     952                  yield from _ctx.emit(msg)
     953              if exc.exceptions is None:
     954                  if exc.stack:
     955                      yield from _ctx.emit('Traceback (most recent call last):\n')
     956                      yield from _ctx.emit(exc.stack.format())
     957                  yield from _ctx.emit(exc.format_exception_only())
     958              elif _ctx.exception_group_depth > self.max_group_depth:
     959                  # exception group, but depth exceeds limit
     960                  yield from _ctx.emit(
     961                      f"... (max_group_depth is {self.max_group_depth})\n")
     962              else:
     963                  # format exception group
     964                  is_toplevel = (_ctx.exception_group_depth == 0)
     965                  if is_toplevel:
     966                      _ctx.exception_group_depth += 1
     967  
     968                  if exc.stack:
     969                      yield from _ctx.emit(
     970                          'Exception Group Traceback (most recent call last):\n',
     971                          margin_char = '+' if is_toplevel else None)
     972                      yield from _ctx.emit(exc.stack.format())
     973  
     974                  yield from _ctx.emit(exc.format_exception_only())
     975                  num_excs = len(exc.exceptions)
     976                  if num_excs <= self.max_group_width:
     977                      n = num_excs
     978                  else:
     979                      n = self.max_group_width + 1
     980                  _ctx.need_close = False
     981                  for i in range(n):
     982                      last_exc = (i == n-1)
     983                      if last_exc:
     984                          # The closing frame may be added by a recursive call
     985                          _ctx.need_close = True
     986  
     987                      if self.max_group_width is not None:
     988                          truncated = (i >= self.max_group_width)
     989                      else:
     990                          truncated = False
     991                      title = f'{i+1}' if not truncated else '...'
     992                      yield (_ctx.indent() +
     993                             ('+-' if i==0 else '  ') +
     994                             f'+---------------- {title} ----------------\n')
     995                      _ctx.exception_group_depth += 1
     996                      if not truncated:
     997                          yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx)
     998                      else:
     999                          remaining = num_excs - self.max_group_width
    1000                          plural = 's' if remaining > 1 else ''
    1001                          yield from _ctx.emit(
    1002                              f"and {remaining} more exception{plural}\n")
    1003  
    1004                      if last_exc and _ctx.need_close:
    1005                          yield (_ctx.indent() +
    1006                                 "+------------------------------------\n")
    1007                          _ctx.need_close = False
    1008                      _ctx.exception_group_depth -= 1
    1009  
    1010                  if is_toplevel:
    1011                      assert _ctx.exception_group_depth == 1
    1012                      _ctx.exception_group_depth = 0
    1013  
    1014  
    1015      def print(self, *, file=None, chain=True):
    1016          """Print the result of self.format(chain=chain) to 'file'."""
    1017          if file is None:
    1018              file = sys.stderr
    1019          for line in self.format(chain=chain):
    1020              print(line, file=file, end="")
    1021  
    1022  
    1023  _MAX_CANDIDATE_ITEMS = 750
    1024  _MAX_STRING_SIZE = 40
    1025  _MOVE_COST = 2
    1026  _CASE_COST = 1
    1027  
    1028  
    1029  def _substitution_cost(ch_a, ch_b):
    1030      if ch_a == ch_b:
    1031          return 0
    1032      if ch_a.lower() == ch_b.lower():
    1033          return _CASE_COST
    1034      return _MOVE_COST
    1035  
    1036  
    1037  def _compute_suggestion_error(exc_value, tb, wrong_name):
    1038      if wrong_name is None or not isinstance(wrong_name, str):
    1039          return None
    1040      if isinstance(exc_value, AttributeError):
    1041          obj = exc_value.obj
    1042          try:
    1043              d = dir(obj)
    1044          except Exception:
    1045              return None
    1046      elif isinstance(exc_value, ImportError):
    1047          try:
    1048              mod = __import__(exc_value.name)
    1049              d = dir(mod)
    1050          except Exception:
    1051              return None
    1052      else:
    1053          assert isinstance(exc_value, NameError)
    1054          # find most recent frame
    1055          if tb is None:
    1056              return None
    1057          while tb.tb_next is not None:
    1058              tb = tb.tb_next
    1059          frame = tb.tb_frame
    1060          d = (
    1061              list(frame.f_locals)
    1062              + list(frame.f_globals)
    1063              + list(frame.f_builtins)
    1064          )
    1065  
    1066          # Check first if we are in a method and the instance
    1067          # has the wrong name as attribute
    1068          if 'self' in frame.f_locals:
    1069              self = frame.f_locals['self']
    1070              if hasattr(self, wrong_name):
    1071                  return f"self.{wrong_name}"
    1072  
    1073      # Compute closest match
    1074  
    1075      if len(d) > _MAX_CANDIDATE_ITEMS:
    1076          return None
    1077      wrong_name_len = len(wrong_name)
    1078      if wrong_name_len > _MAX_STRING_SIZE:
    1079          return None
    1080      best_distance = wrong_name_len
    1081      suggestion = None
    1082      for possible_name in d:
    1083          if possible_name == wrong_name:
    1084              # A missing attribute is "found". Don't suggest it (see GH-88821).
    1085              continue
    1086          # No more than 1/3 of the involved characters should need changed.
    1087          max_distance = (len(possible_name) + wrong_name_len + 3) * _MOVE_COST // 6
    1088          # Don't take matches we've already beaten.
    1089          max_distance = min(max_distance, best_distance - 1)
    1090          current_distance = _levenshtein_distance(wrong_name, possible_name, max_distance)
    1091          if current_distance > max_distance:
    1092              continue
    1093          if not suggestion or current_distance < best_distance:
    1094              suggestion = possible_name
    1095              best_distance = current_distance
    1096      return suggestion
    1097  
    1098  
    1099  def _levenshtein_distance(a, b, max_cost):
    1100      # A Python implementation of Python/suggestions.c:levenshtein_distance.
    1101  
    1102      # Both strings are the same
    1103      if a == b:
    1104          return 0
    1105  
    1106      # Trim away common affixes
    1107      pre = 0
    1108      while a[pre:] and b[pre:] and a[pre] == b[pre]:
    1109          pre += 1
    1110      a = a[pre:]
    1111      b = b[pre:]
    1112      post = 0
    1113      while a[:post or None] and b[:post or None] and a[post-1] == b[post-1]:
    1114          post -= 1
    1115      a = a[:post or None]
    1116      b = b[:post or None]
    1117      if not a or not b:
    1118          return _MOVE_COST * (len(a) + len(b))
    1119      if len(a) > _MAX_STRING_SIZE or len(b) > _MAX_STRING_SIZE:
    1120          return max_cost + 1
    1121  
    1122      # Prefer shorter buffer
    1123      if len(b) < len(a):
    1124          a, b = b, a
    1125  
    1126      # Quick fail when a match is impossible
    1127      if (len(b) - len(a)) * _MOVE_COST > max_cost:
    1128          return max_cost + 1
    1129  
    1130      # Instead of producing the whole traditional len(a)-by-len(b)
    1131      # matrix, we can update just one row in place.
    1132      # Initialize the buffer row
    1133      row = list(range(_MOVE_COST, _MOVE_COST * (len(a) + 1), _MOVE_COST))
    1134  
    1135      result = 0
    1136      for bindex in range(len(b)):
    1137          bchar = b[bindex]
    1138          distance = result = bindex * _MOVE_COST
    1139          minimum = sys.maxsize
    1140          for index in range(len(a)):
    1141              # 1) Previous distance in this row is cost(b[:b_index], a[:index])
    1142              substitute = distance + _substitution_cost(bchar, a[index])
    1143              # 2) cost(b[:b_index], a[:index+1]) from previous row
    1144              distance = row[index]
    1145              # 3) existing result is cost(b[:b_index+1], a[index])
    1146  
    1147              insert_delete = min(result, distance) + _MOVE_COST
    1148              result = min(insert_delete, substitute)
    1149  
    1150              # cost(b[:b_index+1], a[:index+1])
    1151              row[index] = result
    1152              if result < minimum:
    1153                  minimum = result
    1154          if minimum > max_cost:
    1155              # Everything in this row is too big, so bail early.
    1156              return max_cost + 1
    1157      return result