Skip to content

Commit 0d85488

Browse files
iritkatrielFidget-Spinner
authored andcommitted
pythongh-102778: Add sys.last_exc, deprecate sys.last_type, sys.last_value,sys.last_traceback (python#102779)
1 parent b019008 commit 0d85488

File tree

18 files changed

+97
-36
lines changed

18 files changed

+97
-36
lines changed

Doc/library/sys.rst

+14-11
Original file line numberDiff line numberDiff line change
@@ -1102,22 +1102,25 @@ always available.
11021102

11031103
.. versionadded:: 3.5
11041104

1105+
.. data:: last_exc
1106+
1107+
This variable is not always defined; it is set to the exception instance
1108+
when an exception is not handled and the interpreter prints an error message
1109+
and a stack traceback. Its intended use is to allow an interactive user to
1110+
import a debugger module and engage in post-mortem debugging without having
1111+
to re-execute the command that caused the error. (Typical use is
1112+
``import pdb; pdb.pm()`` to enter the post-mortem debugger; see :mod:`pdb`
1113+
module for more information.)
1114+
1115+
.. versionadded:: 3.12
11051116

11061117
.. data:: last_type
11071118
last_value
11081119
last_traceback
11091120

1110-
These three variables are not always defined; they are set when an exception is
1111-
not handled and the interpreter prints an error message and a stack traceback.
1112-
Their intended use is to allow an interactive user to import a debugger module
1113-
and engage in post-mortem debugging without having to re-execute the command
1114-
that caused the error. (Typical use is ``import pdb; pdb.pm()`` to enter the
1115-
post-mortem debugger; see :mod:`pdb` module for
1116-
more information.)
1117-
1118-
The meaning of the variables is the same as that of the return values from
1119-
:func:`exc_info` above.
1120-
1121+
These three variables are deprecated; use :data:`sys.last_exc` instead.
1122+
They hold the legacy representation of ``sys.last_exc``, as returned
1123+
from :func:`exc_info` above.
11211124

11221125
.. data:: maxsize
11231126

Doc/whatsnew/3.12.rst

+10
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,12 @@ sys
397397
with contributions from Gregory P. Smith [Google] and Mark Shannon
398398
in :gh:`96123`.)
399399

400+
* Add :data:`sys.last_exc` which holds the last unhandled exception that
401+
was raised (for post-mortem debugging use cases). Deprecate the
402+
three fields that have the same information in its legacy form:
403+
:data:`sys.last_type`, :data:`sys.last_value` and :data:`sys.last_traceback`.
404+
(Contributed by Irit Katriel in :gh:`102778`.)
405+
400406

401407
Optimizations
402408
=============
@@ -488,6 +494,10 @@ Deprecated
488494
contain the creation time, which is also available in the new ``st_birthtime``
489495
field. (Contributed by Steve Dower in :gh:`99726`.)
490496

497+
* The :data:`sys.last_type`, :data:`sys.last_value` and :data:`sys.last_traceback`
498+
fields are deprecated. Use :data:`sys.last_exc` instead.
499+
(Contributed by Irit Katriel in :gh:`102778`.)
500+
491501
Pending Removal in Python 3.13
492502
------------------------------
493503

Lib/code.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def showsyntaxerror(self, filename=None):
106106
107107
"""
108108
type, value, tb = sys.exc_info()
109+
sys.last_exc = value
109110
sys.last_type = type
110111
sys.last_value = value
111112
sys.last_traceback = tb
@@ -119,7 +120,7 @@ def showsyntaxerror(self, filename=None):
119120
else:
120121
# Stuff in the right filename
121122
value = SyntaxError(msg, (filename, lineno, offset, line))
122-
sys.last_value = value
123+
sys.last_exc = sys.last_value = value
123124
if sys.excepthook is sys.__excepthook__:
124125
lines = traceback.format_exception_only(type, value)
125126
self.write(''.join(lines))
@@ -138,6 +139,7 @@ def showtraceback(self):
138139
"""
139140
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
140141
sys.last_traceback = last_tb
142+
sys.last_exc = ei[1]
141143
try:
142144
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
143145
if sys.excepthook is sys.__excepthook__:

Lib/dis.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,10 @@ def distb(tb=None, *, file=None, show_caches=False, adaptive=False):
132132
"""Disassemble a traceback (default: last traceback)."""
133133
if tb is None:
134134
try:
135-
tb = sys.last_traceback
135+
if hasattr(sys, 'last_exc'):
136+
tb = sys.last_exc.__traceback__
137+
else:
138+
tb = sys.last_traceback
136139
except AttributeError:
137140
raise RuntimeError("no last traceback to disassemble") from None
138141
while tb.tb_next: tb = tb.tb_next

Lib/idlelib/idle_test/test_stackviewer.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def setUpClass(cls):
1919
except NameError:
2020
svs.last_type, svs.last_value, svs.last_traceback = (
2121
sys.exc_info())
22+
svs.last_exc = svs.last_value
2223

2324
requires('gui')
2425
cls.root = Tk()
@@ -27,7 +28,7 @@ def setUpClass(cls):
2728
@classmethod
2829
def tearDownClass(cls):
2930
svs = stackviewer.sys
30-
del svs.last_traceback, svs.last_type, svs.last_value
31+
del svs.last_exc, svs.last_traceback, svs.last_type, svs.last_value
3132

3233
cls.root.update_idletasks()
3334
## for id in cls.root.tk.call('after', 'info'):

Lib/idlelib/pyshell.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1367,11 +1367,14 @@ def open_stack_viewer(self, event=None):
13671367
if self.interp.rpcclt:
13681368
return self.interp.remote_stack_viewer()
13691369
try:
1370-
sys.last_traceback
1370+
if hasattr(sys, 'last_exc'):
1371+
sys.last_exc.__traceback__
1372+
else:
1373+
sys.last_traceback
13711374
except:
13721375
messagebox.showerror("No stack trace",
13731376
"There is no stack trace yet.\n"
1374-
"(sys.last_traceback is not defined)",
1377+
"(sys.last_exc and sys.last_traceback are not defined)",
13751378
parent=self.text)
13761379
return
13771380
from idlelib.stackviewer import StackBrowser

Lib/idlelib/run.py

+2
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ def print_exception():
239239
efile = sys.stderr
240240
typ, val, tb = excinfo = sys.exc_info()
241241
sys.last_type, sys.last_value, sys.last_traceback = excinfo
242+
sys.last_exc = val
242243
seen = set()
243244

244245
def print_exc(typ, exc, tb):
@@ -629,6 +630,7 @@ def stackviewer(self, flist_oid=None):
629630
flist = self.rpchandler.get_remote_proxy(flist_oid)
630631
while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
631632
tb = tb.tb_next
633+
sys.last_exc = val
632634
sys.last_type = typ
633635
sys.last_value = val
634636
item = stackviewer.StackTreeItem(flist, tb)

Lib/idlelib/stackviewer.py

+15-6
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ def __init__(self, flist=None, tb=None):
2727

2828
def get_stack(self, tb):
2929
if tb is None:
30-
tb = sys.last_traceback
30+
if hasattr(sys, 'last_exc'):
31+
tb = sys.last_exc.__traceback__
32+
else:
33+
tb = sys.last_traceback
3134
stack = []
3235
if tb and tb.tb_frame is None:
3336
tb = tb.tb_next
@@ -37,11 +40,15 @@ def get_stack(self, tb):
3740
return stack
3841

3942
def get_exception(self):
40-
type = sys.last_type
41-
value = sys.last_value
42-
if hasattr(type, "__name__"):
43-
type = type.__name__
44-
s = str(type)
43+
if hasattr(sys, 'last_exc'):
44+
typ = type(sys.last_exc)
45+
value = sys.last_exc
46+
else:
47+
typ = sys.last_type
48+
value = sys.last_value
49+
if hasattr(typ, "__name__"):
50+
typ = typ.__name__
51+
s = str(typ)
4552
if value is not None:
4653
s = s + ": " + str(value)
4754
return s
@@ -136,13 +143,15 @@ def _stack_viewer(parent): # htest #
136143
except NameError:
137144
exc_type, exc_value, exc_tb = sys.exc_info()
138145
# inject stack trace to sys
146+
sys.last_exc = exc_value
139147
sys.last_type = exc_type
140148
sys.last_value = exc_value
141149
sys.last_traceback = exc_tb
142150

143151
StackBrowser(top, flist=flist, top=top, tb=exc_tb)
144152

145153
# restore sys to original state
154+
del sys.last_exc
146155
del sys.last_type
147156
del sys.last_value
148157
del sys.last_traceback

Lib/pdb.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1739,7 +1739,11 @@ def post_mortem(t=None):
17391739

17401740
def pm():
17411741
"""Enter post-mortem debugging of the traceback found in sys.last_traceback."""
1742-
post_mortem(sys.last_traceback)
1742+
if hasattr(sys, 'last_exc'):
1743+
tb = sys.last_exc.__traceback__
1744+
else:
1745+
tb = sys.last_traceback
1746+
post_mortem(tb)
17431747

17441748

17451749
# Main program for testing

Lib/pydoc_data/topics.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4799,7 +4799,7 @@
47994799
'pdb.pm()\n'
48004800
'\n'
48014801
' Enter post-mortem debugging of the traceback found in\n'
4802-
' "sys.last_traceback".\n'
4802+
' "sys.last_exc".\n'
48034803
'\n'
48044804
'The "run*" functions and "set_trace()" are aliases for '
48054805
'instantiating\n'
@@ -13858,7 +13858,7 @@
1385813858
'if\n'
1385913859
' the interpreter is interactive, it is also made available to '
1386013860
'the\n'
13861-
' user as "sys.last_traceback".\n'
13861+
' user as "sys.last_exc".\n'
1386213862
'\n'
1386313863
' For explicitly created tracebacks, it is up to the creator '
1386413864
'of\n'

Lib/test/test_dis.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,10 @@ def test_disassemble_try_finally(self):
10261026
self.do_disassembly_test(_tryfinallyconst, dis_tryfinallyconst)
10271027

10281028
def test_dis_none(self):
1029+
try:
1030+
del sys.last_exc
1031+
except AttributeError:
1032+
pass
10291033
try:
10301034
del sys.last_traceback
10311035
except AttributeError:
@@ -1043,7 +1047,7 @@ def test_dis_traceback(self):
10431047
1/0
10441048
except Exception as e:
10451049
tb = e.__traceback__
1046-
sys.last_traceback = tb
1050+
sys.last_exc = e
10471051

10481052
tb_dis = self.get_disassemble_as_string(tb.tb_frame.f_code, tb.tb_lasti)
10491053
self.do_disassembly_test(None, tb_dis, True)
@@ -1900,6 +1904,10 @@ def test_findlabels(self):
19001904

19011905
class TestDisTraceback(DisTestBase):
19021906
def setUp(self) -> None:
1907+
try: # We need to clean up existing tracebacks
1908+
del sys.last_exc
1909+
except AttributeError:
1910+
pass
19031911
try: # We need to clean up existing tracebacks
19041912
del sys.last_traceback
19051913
except AttributeError:

Lib/test/test_ttk/test_extensions.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ def test_widget_destroy(self):
4545
# value which causes the tracing callback to be called and then
4646
# it tries calling instance attributes not yet defined.
4747
ttk.LabeledScale(self.root, variable=myvar)
48-
if hasattr(sys, 'last_type'):
48+
if hasattr(sys, 'last_exc'):
49+
self.assertNotEqual(type(sys.last_exc), tkinter.TclError)
50+
elif hasattr(sys, 'last_type'):
4951
self.assertNotEqual(sys.last_type, tkinter.TclError)
5052

5153
def test_initialization(self):

Lib/tkinter/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -2400,6 +2400,7 @@ def report_callback_exception(self, exc, val, tb):
24002400
should when sys.stderr is None."""
24012401
import traceback
24022402
print("Exception in Tkinter callback", file=sys.stderr)
2403+
sys.last_exc = val
24032404
sys.last_type = exc
24042405
sys.last_value = val
24052406
sys.last_traceback = tb

Lib/traceback.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -179,20 +179,24 @@ def _safe_string(value, what, func=str):
179179
# --
180180

181181
def print_exc(limit=None, file=None, chain=True):
182-
"""Shorthand for 'print_exception(*sys.exc_info(), limit, file)'."""
182+
"""Shorthand for 'print_exception(*sys.exc_info(), limit, file, chain)'."""
183183
print_exception(*sys.exc_info(), limit=limit, file=file, chain=chain)
184184

185185
def format_exc(limit=None, chain=True):
186186
"""Like print_exc() but return a string."""
187187
return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain))
188188

189189
def print_last(limit=None, file=None, chain=True):
190-
"""This is a shorthand for 'print_exception(sys.last_type,
191-
sys.last_value, sys.last_traceback, limit, file)'."""
192-
if not hasattr(sys, "last_type"):
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"):
193192
raise ValueError("no last exception")
194-
print_exception(sys.last_type, sys.last_value, sys.last_traceback,
195-
limit, file, chain)
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+
196200

197201
#
198202
# Printing and Extracting Stacks.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :data:`sys.last_exc` and deprecate :data:`sys.last_type`, :data:`sys.last_value`
2+
and :data:`sys.last_traceback`,
3+
which hold the same information in its legacy form.

Python/pylifecycle.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1304,7 +1304,7 @@ finalize_modules_delete_special(PyThreadState *tstate, int verbose)
13041304
{
13051305
// List of names to clear in sys
13061306
static const char * const sys_deletes[] = {
1307-
"path", "argv", "ps1", "ps2",
1307+
"path", "argv", "ps1", "ps2", "last_exc",
13081308
"last_type", "last_value", "last_traceback",
13091309
"__interactivehook__",
13101310
// path_hooks and path_importer_cache are cleared

Python/pythonrun.c

+4
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,10 @@ _PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars)
776776
}
777777

778778
if (set_sys_last_vars) {
779+
if (_PySys_SetAttr(&_Py_ID(last_exc), exc) < 0) {
780+
_PyErr_Clear(tstate);
781+
}
782+
/* Legacy version: */
779783
if (_PySys_SetAttr(&_Py_ID(last_type), typ) < 0) {
780784
_PyErr_Clear(tstate);
781785
}

Python/sysmodule.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -2670,11 +2670,13 @@ stderr -- standard error object; used for error messages\n\
26702670
By assigning other file objects (or objects that behave like files)\n\
26712671
to these, it is possible to redirect all of the interpreter's I/O.\n\
26722672
\n\
2673+
last_exc - the last uncaught exception\n\
2674+
Only available in an interactive session after a\n\
2675+
traceback has been printed.\n\
26732676
last_type -- type of last uncaught exception\n\
26742677
last_value -- value of last uncaught exception\n\
26752678
last_traceback -- traceback of last uncaught exception\n\
2676-
These three are only available in an interactive session after a\n\
2677-
traceback has been printed.\n\
2679+
These three are the (deprecated) legacy representation of last_exc.\n\
26782680
"
26792681
)
26802682
/* concatenating string here */

0 commit comments

Comments
 (0)