Skip to content

Commit 979423f

Browse files
committed
add recent_first parameter to traceback functions
1 parent a0b4b70 commit 979423f

File tree

2 files changed

+102
-82
lines changed

2 files changed

+102
-82
lines changed

Lib/test/test_traceback.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,12 +564,13 @@ def test_signatures(self):
564564
self.assertEqual(
565565
str(inspect.signature(traceback.print_exception)),
566566
('(exc, /, value=<implicit>, tb=<implicit>, '
567-
'limit=None, file=None, chain=True, show_lines=True, **kwargs)'))
567+
'limit=None, file=None, chain=True, *, '
568+
'show_lines=True, recent_first=False, **kwargs)'))
568569

569570
self.assertEqual(
570571
str(inspect.signature(traceback.format_exception)),
571572
('(exc, /, value=<implicit>, tb=<implicit>, limit=None, '
572-
'chain=True, show_lines=True, **kwargs)'))
573+
'chain=True, *, show_lines=True, recent_first=False, **kwargs)'))
573574

574575
self.assertEqual(
575576
str(inspect.signature(traceback.format_exception_only)),

Lib/traceback.py

Lines changed: 99 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,15 @@ def print_list(extracted_list, file=None, *, show_lines=True):
3030
"""Print the list of tuples as returned by extract_tb() or
3131
extract_stack() as a formatted stack trace to the given file.
3232
33-
If show_lines is False, source code lines are not included in the output.
33+
To print in "recent call first" order, call "extracted_list.reverse()"
34+
before passing it to this function.
35+
36+
If 'show_lines' is false, source code lines are not included in the output.
3437
"""
3538
if file is None:
3639
file = sys.stderr
37-
for item in StackSummary.from_list(extracted_list).format(show_lines=show_lines):
40+
for item in StackSummary.from_list(extracted_list).format(
41+
show_lines=show_lines):
3842
print(item, file=file, end="")
3943

4044
def format_list(extracted_list, *, show_lines=True):
@@ -44,37 +48,41 @@ def format_list(extracted_list, *, show_lines=True):
4448
extract_tb() or extract_stack(), return a list of strings ready
4549
for printing.
4650
47-
Each string in the resulting list corresponds to the item with the
48-
same index in the argument list. Each string ends in a newline;
49-
the strings may contain internal newlines as well, for those items
50-
whose source text line is not None.
51+
Each string ends in a newline; the strings may contain internal newlines as
52+
well, for those items whose source text line is not None.
5153
52-
If show_lines is False, source code lines are not included in the output.
54+
If 'show_lines' is false, source code lines are not included in the output.
5355
"""
5456
return StackSummary.from_list(extracted_list).format(show_lines=show_lines)
5557

5658
#
5759
# Printing and Extracting Tracebacks.
5860
#
5961

60-
def print_tb(tb, limit=None, file=None, *, show_lines=True):
62+
def print_tb(tb, limit=None, file=None, *, show_lines=True, recent_first=False):
6163
"""Print up to 'limit' stack trace entries from the traceback 'tb'.
6264
6365
If 'limit' is omitted or None, all entries are printed. If 'file'
6466
is omitted or None, the output goes to sys.stderr; otherwise
6567
'file' should be an open file or file-like object with a write()
6668
method.
6769
68-
If show_lines is False, source code lines are not included in the output.
70+
If 'show_lines' is false, source code lines are not included in the output.
71+
If 'recent_first' is true, the stack trace is printed in "most recent call
72+
first" order.
6973
"""
70-
print_list(extract_tb(tb, limit=limit), file=file, show_lines=show_lines)
74+
tblist = extract_tb(tb, limit=limit)
75+
if recent_first:
76+
tblist.reverse()
77+
print_list(tblist, file=file, show_lines=show_lines)
7178

72-
def format_tb(tb, limit=None, *, show_lines=True):
73-
"""A shorthand for 'format_list(extract_tb(tb, limit))'.
79+
def format_tb(tb, limit=None, *, show_lines=True, recent_first=False):
80+
"""A shorthand for 'format_list(extract_tb(tb, limit))'."""
7481

75-
If show_lines is False, source code lines are not included in the output.
76-
"""
77-
return extract_tb(tb, limit=limit).format(show_lines=show_lines)
82+
tblist = extract_tb(tb, limit=limit)
83+
if recent_first:
84+
tblist.reverse()
85+
return tblist.format(show_lines=show_lines)
7886

7987
def extract_tb(tb, limit=None):
8088
"""
@@ -90,7 +98,7 @@ def extract_tb(tb, limit=None):
9098
whitespace stripped; if the source is not available it is None.
9199
"""
92100
return StackSummary._extract_from_extended_frame_gen(
93-
_walk_tb_with_full_positions(tb), limit=limit, lookup_lines=False)
101+
_walk_tb_with_full_positions(tb), limit=limit)
94102

95103
#
96104
# Exception formatting and output.
@@ -126,8 +134,9 @@ def _parse_value_tb(exc, value, tb):
126134
return value, tb
127135

128136

129-
def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
130-
file=None, chain=True, show_lines=True, **kwargs):
137+
def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None,
138+
file=None, chain=True, *, show_lines=True,
139+
recent_first=False, **kwargs):
131140
"""Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
132141
133142
This differs from print_tb() in the following ways: (1) if
@@ -138,13 +147,15 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
138147
occurred with a caret on the next line indicating the approximate
139148
position of the error.
140149
141-
If show_lines is False, source code lines are not included in the output.
150+
If 'show_lines' is false, source code lines are not included in the output.
151+
If 'recent_first' is true, exception is printed first and traceback is shown
152+
by "most recent call first" order.
142153
"""
143154
colorize = kwargs.get("colorize", False)
144155
value, tb = _parse_value_tb(exc, value, tb)
145-
te = TracebackException(type(value), value, tb, limit=limit, compact=True,
146-
lookup_lines=show_lines)
147-
te.print(file=file, chain=chain, colorize=colorize, show_lines=show_lines)
156+
te = TracebackException(type(value), value, tb, limit=limit, compact=True)
157+
te.print(file=file, chain=chain, colorize=colorize,
158+
show_lines=show_lines, recent_first=recent_first)
148159

149160

150161
BUILTIN_EXCEPTION_LIMIT = object()
@@ -156,23 +167,21 @@ def _print_exception_bltin(exc, /):
156167
return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize)
157168

158169

159-
def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
160-
chain=True, show_lines=True, **kwargs):
170+
def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None,
171+
chain=True, *, show_lines=True, recent_first=False, **kwargs):
161172
"""Format a stack trace and the exception information.
162173
163174
The arguments have the same meaning as the corresponding arguments
164175
to print_exception(). The return value is a list of strings, each
165176
ending in a newline and some containing internal newlines. When
166177
these lines are concatenated and printed, exactly the same text is
167178
printed as does print_exception().
168-
169-
If show_lines is False, source code lines are not included in the output.
170179
"""
171180
colorize = kwargs.get("colorize", False)
172181
value, tb = _parse_value_tb(exc, value, tb)
173-
te = TracebackException(type(value), value, tb, limit=limit, compact=True,
174-
lookup_lines=show_lines)
175-
return list(te.format(chain=chain, colorize=colorize, show_lines=show_lines))
182+
te = TracebackException(type(value), value, tb, limit=limit, compact=True)
183+
return list(te.format(chain=chain, colorize=colorize,
184+
show_lines=show_lines, recent_first=recent_first))
176185

177186

178187
def format_exception_only(exc, /, value=_sentinel, *, show_group=False, **kwargs):
@@ -221,61 +230,63 @@ def _safe_string(value, what, func=str):
221230

222231
# --
223232

224-
def print_exc(limit=None, file=None, chain=True, *, show_lines=True):
225-
"""Shorthand for 'print_exception(sys.exception(), limit=limit, file=file, chain=chain)'.
226-
227-
If show_lines is False, source code lines are not included in the output.
228-
"""
229-
print_exception(sys.exception(), limit=limit, file=file, chain=chain, show_lines=show_lines)
230-
231-
def format_exc(limit=None, chain=True, *, show_lines=True):
232-
"""Like print_exc() but return a string.
233-
234-
If show_lines is False, source code lines are not included in the output.
235-
"""
236-
return "".join(format_exception(sys.exception(), limit=limit, chain=chain, show_lines=show_lines))
237-
238-
def print_last(limit=None, file=None, chain=True, *, show_lines=True):
239-
"""This is a shorthand for 'print_exception(sys.last_exc, limit=limit, file=file, chain=chain)'.
240-
241-
If show_lines is False, source code lines are not included in the output.
242-
"""
233+
def print_exc(limit=None, file=None, chain=True, *, show_lines=True,
234+
recent_first=False):
235+
"""Shorthand for 'print_exception(sys.exception(), limit=limit, file=file, chain=chain, ...)'."""
236+
print_exception(sys.exception(), limit=limit, file=file, chain=chain,
237+
show_lines=show_lines, recent_first=recent_first)
238+
239+
def format_exc(limit=None, chain=True, *, show_lines=True, recent_first=False):
240+
"""Like print_exc() but return a string."""
241+
return "".join(format_exception(
242+
sys.exception(), limit=limit, chain=chain,
243+
show_lines=show_lines, recent_first=recent_first))
244+
245+
def print_last(limit=None, file=None, chain=True, *, show_lines=True,
246+
recent_first=False):
247+
"""This is a shorthand for 'print_exception(sys.last_exc, limit=limit, file=file, chain=chain, ...)'."""
243248
if not hasattr(sys, "last_exc") and not hasattr(sys, "last_type"):
244249
raise ValueError("no last exception")
245250

246251
if hasattr(sys, "last_exc"):
247-
print_exception(sys.last_exc, limit=limit, file=file, chain=chain, show_lines=show_lines)
252+
print_exception(sys.last_exc, limit=limit, file=file, chain=chain,
253+
show_lines=show_lines, recent_first=recent_first)
248254
else:
249255
print_exception(sys.last_type, sys.last_value, sys.last_traceback,
250-
limit=limit, file=file, chain=chain, show_lines=show_lines)
256+
limit=limit, file=file, chain=chain,
257+
show_lines=show_lines, recent_first=recent_first)
251258

252259

253260
#
254261
# Printing and Extracting Stacks.
255262
#
256263

257-
def print_stack(f=None, limit=None, file=None, *, show_lines=True):
264+
def print_stack(f=None, limit=None, file=None, *, show_lines=True, recent_first=False):
258265
"""Print a stack trace from its invocation point.
259266
260267
The optional 'f' argument can be used to specify an alternate
261268
stack frame at which to start. The optional 'limit' and 'file'
262269
arguments have the same meaning as for print_exception().
263270
264-
If show_lines is False, source code lines are not included in the output.
271+
If 'show_lines' is false, source code lines are not included in the output.
272+
If 'recent_first' is true, stack is printed by "most recent call first" order.
265273
"""
266274
if f is None:
267275
f = sys._getframe().f_back
268-
print_list(extract_stack(f, limit=limit), file=file, show_lines=show_lines)
276+
stack = extract_stack(f, limit=limit)
277+
if recent_first:
278+
stack.reverse()
279+
print_list(stack, file=file, show_lines=show_lines)
269280

270281

271-
def format_stack(f=None, limit=None, *, show_lines=True):
272-
"""Shorthand for 'format_list(extract_stack(f, limit))'.
273-
274-
If show_lines is False, source code lines are not included in the output.
275-
"""
282+
def format_stack(f=None, limit=None, *, show_lines=True, recent_first=False):
283+
"""Shorthand for 'format_list(extract_stack(f, limit), show_lines)'."""
276284
if f is None:
277285
f = sys._getframe().f_back
278-
return format_list(extract_stack(f, limit=limit), show_lines=show_lines)
286+
stack = extract_stack(f, limit=limit)
287+
if recent_first:
288+
stack.reverse()
289+
return format_list(stack, show_lines=show_lines)
279290

280291

281292
def extract_stack(f=None, limit=None):
@@ -289,7 +300,8 @@ def extract_stack(f=None, limit=None):
289300
"""
290301
if f is None:
291302
f = sys._getframe().f_back
292-
stack = StackSummary.extract(walk_stack(f), limit=limit, lookup_lines=False)
303+
stack = StackSummary.extract(walk_stack(f), limit=limit)
304+
# Traceback should use "recent call last" order.
293305
stack.reverse()
294306
return stack
295307

@@ -465,7 +477,7 @@ class StackSummary(list):
465477
"""A list of FrameSummary objects, representing a stack of frames."""
466478

467479
@classmethod
468-
def extract(klass, frame_gen, *, limit=None, lookup_lines=True,
480+
def extract(klass, frame_gen, *, limit=None, lookup_lines=False,
469481
capture_locals=False):
470482
"""Create a StackSummary from a traceback or stack object.
471483
@@ -488,7 +500,7 @@ def extended_frame_gen():
488500

489501
@classmethod
490502
def _extract_from_extended_frame_gen(klass, frame_gen, *, limit=None,
491-
lookup_lines=True, capture_locals=False):
503+
lookup_lines=False, capture_locals=False):
492504
# Same as extract but operates on a frame generator that yields
493505
# (frame, (lineno, end_lineno, colno, end_colno)) in the stack.
494506
# Only lineno is required, the remaining fields can be None if the
@@ -560,8 +572,6 @@ def format_frame_summary(self, frame_summary, *, show_lines=True, **kwargs):
560572
561573
Returns a string representing one frame involved in the stack. This
562574
gets called for every frame to be printed in the stack summary.
563-
564-
If show_lines is False, source code lines are not included in the output.
565575
"""
566576
colorize = kwargs.get("colorize", False)
567577
row = []
@@ -785,8 +795,6 @@ def format(self, *, show_lines=True, **kwargs):
785795
For long sequences of the same frame and line, the first few
786796
repetitions are shown, followed by a summary line stating the exact
787797
number of further repetitions.
788-
789-
If show_lines is False, source code lines are not included in the output.
790798
"""
791799
colorize = kwargs.get("colorize", False)
792800
result = []
@@ -1076,7 +1084,7 @@ class TracebackException:
10761084
"""
10771085

10781086
def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
1079-
lookup_lines=True, capture_locals=False, compact=False,
1087+
lookup_lines=False, capture_locals=False, compact=False,
10801088
max_group_width=15, max_group_depth=10, save_exc_type=True, _seen=None):
10811089
# NB: we need to accept exc_traceback, exc_value, exc_traceback to
10821090
# permit backwards compat with the existing API, otherwise we
@@ -1499,7 +1507,7 @@ def _format_syntax_error(self, stype, **kwargs):
14991507
filename_suffix,
15001508
)
15011509

1502-
def format(self, *, chain=True, show_lines=True, _ctx=None, **kwargs):
1510+
def format(self, *, chain=True, show_lines=True, _ctx=None, recent_first=False, **kwargs):
15031511
"""Format the exception.
15041512
15051513
If chain is not *True*, *__cause__* and *__context__* will not be formatted.
@@ -1510,8 +1518,6 @@ def format(self, *, chain=True, show_lines=True, _ctx=None, **kwargs):
15101518
15111519
The message indicating which exception occurred is always the last
15121520
string in the output.
1513-
1514-
If show_lines is False, source code lines are not included in the output.
15151521
"""
15161522
colorize = kwargs.get("colorize", False)
15171523
if _ctx is None:
@@ -1541,10 +1547,16 @@ def format(self, *, chain=True, show_lines=True, _ctx=None, **kwargs):
15411547
if msg is not None:
15421548
yield from _ctx.emit(msg)
15431549
if exc.exceptions is None:
1544-
if exc.stack:
1550+
if not recent_first and exc.stack:
15451551
yield from _ctx.emit('Traceback (most recent call last):\n')
1546-
yield from _ctx.emit(exc.stack.format(show_lines=show_lines, colorize=colorize))
1552+
yield from _ctx.emit(exc.stack.format(
1553+
show_lines=show_lines, colorize=colorize))
15471554
yield from _ctx.emit(exc.format_exception_only(colorize=colorize))
1555+
if recent_first and exc.stack:
1556+
yield from _ctx.emit('Traceback (most recent call first):\n')
1557+
reversed_stack = StackSummary(reversed(self.stack))
1558+
yield from _ctx.emit(reversed_stack.format(
1559+
show_lines=show_lines, colorize=colorize))
15481560
elif _ctx.exception_group_depth > self.max_group_depth:
15491561
# exception group, but depth exceeds limit
15501562
yield from _ctx.emit(
@@ -1555,13 +1567,22 @@ def format(self, *, chain=True, show_lines=True, _ctx=None, **kwargs):
15551567
if is_toplevel:
15561568
_ctx.exception_group_depth += 1
15571569

1558-
if exc.stack:
1570+
if not recent_first and exc.stack:
15591571
yield from _ctx.emit(
15601572
'Exception Group Traceback (most recent call last):\n',
15611573
margin_char = '+' if is_toplevel else None)
1562-
yield from _ctx.emit(exc.stack.format(show_lines=show_lines, colorize=colorize))
1574+
yield from _ctx.emit(exc.stack.format(
1575+
show_lines=show_lines, colorize=colorize))
15631576

15641577
yield from _ctx.emit(exc.format_exception_only(colorize=colorize))
1578+
if recent_first and exc.stack:
1579+
yield from _ctx.emit(
1580+
'Exception Group Traceback (most recent call first):\n',
1581+
margin_char = '+' if is_toplevel else None)
1582+
reversed_stack = StackSummary(reversed(self.stack))
1583+
yield from _ctx.emit(reversed_stack.format(
1584+
show_lines=show_lines, colorize=colorize))
1585+
15651586
num_excs = len(exc.exceptions)
15661587
if num_excs <= self.max_group_width:
15671588
n = num_excs
@@ -1602,15 +1623,13 @@ def format(self, *, chain=True, show_lines=True, _ctx=None, **kwargs):
16021623
_ctx.exception_group_depth = 0
16031624

16041625

1605-
def print(self, *, file=None, chain=True, show_lines=True, **kwargs):
1606-
"""Print the result of self.format(chain=chain) to 'file'.
1607-
1608-
If show_lines is False, source code lines are not included in the output.
1609-
"""
1626+
def print(self, *, file=None, chain=True, show_lines=True, recent_first=False, **kwargs):
1627+
"""Print the result of self.format(chain=chain) to 'file'."""
16101628
colorize = kwargs.get("colorize", False)
16111629
if file is None:
16121630
file = sys.stderr
1613-
for line in self.format(chain=chain, show_lines=show_lines, colorize=colorize):
1631+
for line in self.format(chain=chain, show_lines=show_lines,
1632+
recent_first=recent_first, colorize=colorize):
16141633
print(line, file=file, end="")
16151634

16161635

0 commit comments

Comments
 (0)