[Python-checkins] bpo-44569: Decouple frame formatting in traceback.py (GH-27038)

pablogsal webhook-mailer at python.org
Fri Jul 16 08:21:25 EDT 2021


https://github.com/python/cpython/commit/8ce3008585feed51bd08ec256a19923940d744d4
commit: 8ce3008585feed51bd08ec256a19923940d744d4
branch: main
author: Ammar Askar <ammar at ammaraskar.com>
committer: pablogsal <Pablogsal at gmail.com>
date: 2021-07-16T13:21:16+01:00
summary:

bpo-44569: Decouple frame formatting in traceback.py (GH-27038)

files:
A Misc/NEWS.d/next/Library/2021-07-08-12-22-54.bpo-44569.KZ02v9.rst
M Doc/library/traceback.rst
M Lib/test/test_traceback.py
M Lib/traceback.py

diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst
index be1f43ea953ed..83d5c8c6fcbd3 100644
--- a/Doc/library/traceback.rst
+++ b/Doc/library/traceback.rst
@@ -353,6 +353,14 @@ capture data for later printing in a lightweight fashion.
       .. versionchanged:: 3.6
          Long sequences of repeated frames are now abbreviated.
 
+   .. method:: format_frame(frame)
+
+      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`.
+
+      .. versionadded:: 3.11
+
 
 :class:`FrameSummary` Objects
 -----------------------------
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 402f773814ec4..4742eb1d2309b 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -1429,6 +1429,21 @@ def some_inner(k, v):
              '    v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 3)
             ], s.format())
 
+    def test_custom_format_frame(self):
+        class CustomStackSummary(traceback.StackSummary):
+            def format_frame(self, frame):
+                return f'{frame.filename}:{frame.lineno}'
+
+        def some_inner():
+            return CustomStackSummary.extract(
+                traceback.walk_stack(None), limit=1)
+
+        s = some_inner()
+        self.assertEqual(
+            s.format(),
+            [f'{__file__}:{some_inner.__code__.co_firstlineno + 1}'])
+
+
 class TestTracebackException(unittest.TestCase):
 
     def test_smoke(self):
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 40d736af56dd7..ae5775d2f3bda 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -449,6 +449,48 @@ 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.
+
+        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()))
+
+            stripped_characters = len(frame._original_line) - len(frame.line.lstrip())
+            if frame.end_lineno == frame.lineno and frame.end_colno != 0:
+                colno = _byte_offset_to_character_offset(frame._original_line, frame.colno)
+                end_colno = _byte_offset_to_character_offset(frame._original_line, frame.end_colno)
+
+                try:
+                    anchors = _extract_caret_anchors_from_line_segment(
+                        frame._original_line[colno - 1:end_colno - 1]
+                    )
+                except Exception:
+                    anchors = None
+
+                row.append('    ')
+                row.append(' ' * (colno - stripped_characters))
+
+                if anchors:
+                    row.append(anchors.primary_char * (anchors.left_end_offset))
+                    row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
+                    row.append(anchors.primary_char * (end_colno - colno - anchors.right_start_offset))
+                else:
+                    row.append('^' * (end_colno - colno))
+
+                row.append('\n')
+
+        if frame.locals:
+            for name, value in sorted(frame.locals.items()):
+                row.append('    {name} = {value}\n'.format(name=name, value=value))
+
+        return ''.join(row)
+
     def format(self):
         """Format the stack ready for printing.
 
@@ -483,40 +525,8 @@ def format(self):
             count += 1
             if count > _RECURSIVE_CUTOFF:
                 continue
-            row = []
-            row.append('  File "{}", line {}, in {}\n'.format(
-                frame.filename, frame.lineno, frame.name))
-            if frame.line:
-                row.append('    {}\n'.format(frame.line.strip()))
-
-                stripped_characters = len(frame._original_line) - len(frame.line.lstrip())
-                if frame.end_lineno == frame.lineno and frame.end_colno != 0:
-                    colno = _byte_offset_to_character_offset(frame._original_line, frame.colno)
-                    end_colno = _byte_offset_to_character_offset(frame._original_line, frame.end_colno)
-
-                    try:
-                        anchors = _extract_caret_anchors_from_line_segment(
-                            frame._original_line[colno - 1:end_colno - 1]
-                        )
-                    except Exception:
-                        anchors = None
-
-                    row.append('    ')
-                    row.append(' ' * (colno - stripped_characters))
-
-                    if anchors:
-                        row.append(anchors.primary_char * (anchors.left_end_offset))
-                        row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
-                        row.append(anchors.primary_char * (end_colno - colno - anchors.right_start_offset))
-                    else:
-                        row.append('^' * (end_colno - colno))
-
-                    row.append('\n')
-
-            if frame.locals:
-                for name, value in sorted(frame.locals.items()):
-                    row.append('    {name} = {value}\n'.format(name=name, value=value))
-            result.append(''.join(row))
+            result.append(self.format_frame(frame))
+
         if count > _RECURSIVE_CUTOFF:
             count -= _RECURSIVE_CUTOFF
             result.append(
diff --git a/Misc/NEWS.d/next/Library/2021-07-08-12-22-54.bpo-44569.KZ02v9.rst b/Misc/NEWS.d/next/Library/2021-07-08-12-22-54.bpo-44569.KZ02v9.rst
new file mode 100644
index 0000000000000..5f693b290dfb8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-07-08-12-22-54.bpo-44569.KZ02v9.rst
@@ -0,0 +1,3 @@
+Added the :func:`StackSummary.format_frame` function in :mod:`traceback`.
+This allows users to customize the way individual lines are formatted in
+tracebacks without re-implementing logic to handle recursive tracebacks.



More information about the Python-checkins mailing list