Skip to content

Commit

Permalink
bpo-45075: distinguish between frame and FrameSummary in traceback mo… (
Browse files Browse the repository at this point in the history
  • Loading branch information
iritkatriel committed Sep 3, 2021
1 parent 6f8bc46 commit 0b58e86
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 39 deletions.
10 changes: 5 additions & 5 deletions Doc/library/traceback.rst
Original file line number Diff line number Diff line change
Expand Up @@ -353,12 +353,12 @@ capture data for later printing in a lightweight fashion.
.. versionchanged:: 3.6
Long sequences of repeated frames are now abbreviated.

.. method:: format_frame(frame)
.. method:: format_frame_summary(frame_summary)

Returns a string for printing one of the frames involved in the stack.
This method gets called for each frame object to be printed in the
:class:`StackSummary`. If it returns ``None``, the frame is omitted
from the output.
This method is called for each :class:`FrameSummary` object to be
printed by :meth:`StackSummary.format`. If it returns ``None``, the
frame is omitted from the output.

.. versionadded:: 3.11

Expand All @@ -368,7 +368,7 @@ capture data for later printing in a lightweight fashion.

.. versionadded:: 3.5

:class:`FrameSummary` objects represent a single frame in a traceback.
A :class:`FrameSummary` object represents a single frame in a traceback.

.. class:: FrameSummary(filename, lineno, name, lookup_line=True, locals=None, line=None)

Expand Down
10 changes: 5 additions & 5 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -1515,8 +1515,8 @@ def some_inner(k, v):

def test_custom_format_frame(self):
class CustomStackSummary(traceback.StackSummary):
def format_frame(self, frame):
return f'{frame.filename}:{frame.lineno}'
def format_frame_summary(self, frame_summary):
return f'{frame_summary.filename}:{frame_summary.lineno}'

def some_inner():
return CustomStackSummary.extract(
Expand All @@ -1540,10 +1540,10 @@ def g():
exc_info = g()

class Skip_G(traceback.StackSummary):
def format_frame(self, frame):
if frame.name == 'g':
def format_frame_summary(self, frame_summary):
if frame_summary.name == 'g':
return None
return super().format_frame(frame)
return super().format_frame_summary(frame_summary)

stack = Skip_G.extract(
traceback.walk_tb(exc_info[2])).format()
Expand Down
62 changes: 33 additions & 29 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def clear_frames(tb):


class FrameSummary:
"""A single frame from a traceback.
"""Information about a single frame from a traceback.
- :attr:`filename` The filename for the frame.
- :attr:`lineno` The line within filename for the frame that was
Expand Down Expand Up @@ -365,15 +365,15 @@ def _get_code_position(code, instruction_index):
_RECURSIVE_CUTOFF = 3 # Also hardcoded in traceback.c.

class StackSummary(list):
"""A stack of frames."""
"""A list of FrameSummary objects, representing a stack of frames."""

@classmethod
def extract(klass, frame_gen, *, limit=None, lookup_lines=True,
capture_locals=False):
"""Create a StackSummary from a traceback or stack object.
:param frame_gen: A generator that yields (frame, lineno) tuples to
include in the stack.
:param frame_gen: A generator that yields (frame, lineno) tuples
whose summaries are to be included in the stack.
:param limit: None to include all frames or the number of frames to
include.
:param lookup_lines: If True, lookup lines for each frame immediately,
Expand All @@ -394,7 +394,7 @@ def _extract_from_extended_frame_gen(klass, frame_gen, *, limit=None,
lookup_lines=True, capture_locals=False):
# Same as extract but operates on a frame generator that yields
# (frame, (lineno, end_lineno, colno, end_colno)) in the stack.
# Only lineno is required, the remaining fields can be empty if the
# Only lineno is required, the remaining fields can be None if the
# information is not available.
if limit is None:
limit = getattr(sys, 'tracebacklimit', None)
Expand Down Expand Up @@ -450,34 +450,38 @@ def from_list(klass, a_list):
result.append(FrameSummary(filename, lineno, name, line=line))
return result

def format_frame(self, frame):
"""Format the lines for a single frame.
def format_frame_summary(self, frame_summary):
"""Format the lines for a single FrameSummary.
Returns a string representing one frame involved in the stack. This
gets called for every frame to be printed in the stack summary.
"""
row = []
row.append(' File "{}", line {}, in {}\n'.format(
frame.filename, frame.lineno, frame.name))
if frame.line:
row.append(' {}\n'.format(frame.line.strip()))
frame_summary.filename, frame_summary.lineno, frame_summary.name))
if frame_summary.line:
row.append(' {}\n'.format(frame_summary.line.strip()))

stripped_characters = len(frame._original_line) - len(frame.line.lstrip())
orig_line_len = len(frame_summary._original_line)
frame_line_len = len(frame_summary.line.lstrip())
stripped_characters = orig_line_len - frame_line_len
if (
frame.colno is not None
and frame.end_colno is not None
frame_summary.colno is not None
and frame_summary.end_colno is not None
):
colno = _byte_offset_to_character_offset(frame._original_line, frame.colno)
end_colno = _byte_offset_to_character_offset(frame._original_line, frame.end_colno)
colno = _byte_offset_to_character_offset(
frame_summary._original_line, frame_summary.colno)
end_colno = _byte_offset_to_character_offset(
frame_summary._original_line, frame_summary.end_colno)

anchors = None
if frame.lineno == frame.end_lineno:
if frame_summary.lineno == frame_summary.end_lineno:
with suppress(Exception):
anchors = _extract_caret_anchors_from_line_segment(
frame._original_line[colno - 1:end_colno - 1]
frame_summary._original_line[colno - 1:end_colno - 1]
)
else:
end_colno = stripped_characters + len(frame.line.strip())
end_colno = stripped_characters + len(frame_summary.line.strip())

row.append(' ')
row.append(' ' * (colno - stripped_characters))
Expand All @@ -491,8 +495,8 @@ def format_frame(self, frame):

row.append('\n')

if frame.locals:
for name, value in sorted(frame.locals.items()):
if frame_summary.locals:
for name, value in sorted(frame_summary.locals.items()):
row.append(' {name} = {value}\n'.format(name=name, value=value))

return ''.join(row)
Expand All @@ -514,27 +518,27 @@ def format(self):
last_line = None
last_name = None
count = 0
for frame in self:
formatted_frame = self.format_frame(frame)
for frame_summary in self:
formatted_frame = self.format_frame_summary(frame_summary)
if formatted_frame is None:
continue
if (last_file is None or last_file != frame.filename or
last_line is None or last_line != frame.lineno or
last_name is None or last_name != frame.name):
if (last_file is None or last_file != frame_summary.filename or
last_line is None or last_line != frame_summary.lineno or
last_name is None or last_name != frame_summary.name):
if count > _RECURSIVE_CUTOFF:
count -= _RECURSIVE_CUTOFF
result.append(
f' [Previous line repeated {count} more '
f'time{"s" if count > 1 else ""}]\n'
)
last_file = frame.filename
last_line = frame.lineno
last_name = frame.name
last_file = frame_summary.filename
last_line = frame_summary.lineno
last_name = frame_summary.name
count = 0
count += 1
if count > _RECURSIVE_CUTOFF:
continue
result.append(self.format_frame(frame))
result.append(formatted_frame)

if count > _RECURSIVE_CUTOFF:
count -= _RECURSIVE_CUTOFF
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Rename :meth:`traceback.StackSummary.format_frame` to
:meth:`traceback.StackSummary.format_frame_summary`. This method was added
for 3.11 so it was not released yet.

Updated code and docs to better distinguish frame and FrameSummary.

0 comments on commit 0b58e86

Please sign in to comment.