Skip to content

Commit

Permalink
pythongh-102778: Add sys.last_exc, deprecate sys.last_type, sys.last_…
Browse files Browse the repository at this point in the history
…value,sys.last_traceback (python#102779)
  • Loading branch information
iritkatriel authored and warsaw committed Apr 11, 2023
1 parent 1087432 commit bb6764d
Show file tree
Hide file tree
Showing 22 changed files with 103 additions and 36 deletions.
25 changes: 14 additions & 11 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1102,22 +1102,25 @@ always available.

.. versionadded:: 3.5

.. data:: last_exc

This variable is not always defined; it is set to the exception instance
when an exception is not handled and the interpreter prints an error message
and a stack traceback. Its intended use is to allow an interactive user to
import a debugger module and engage in post-mortem debugging without having
to re-execute the command that caused the error. (Typical use is
``import pdb; pdb.pm()`` to enter the post-mortem debugger; see :mod:`pdb`
module for more information.)

.. versionadded:: 3.12

.. data:: last_type
last_value
last_traceback

These three variables are not always defined; they are set when an exception is
not handled and the interpreter prints an error message and a stack traceback.
Their intended use is to allow an interactive user to import a debugger module
and engage in post-mortem debugging without having to re-execute the command
that caused the error. (Typical use is ``import pdb; pdb.pm()`` to enter the
post-mortem debugger; see :mod:`pdb` module for
more information.)

The meaning of the variables is the same as that of the return values from
:func:`exc_info` above.

These three variables are deprecated; use :data:`sys.last_exc` instead.
They hold the legacy representation of ``sys.last_exc``, as returned
from :func:`exc_info` above.

.. data:: maxsize

Expand Down
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,12 @@ sys
with contributions from Gregory P. Smith [Google] and Mark Shannon
in :gh:`96123`.)

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


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

* The :data:`sys.last_type`, :data:`sys.last_value` and :data:`sys.last_traceback`
fields are deprecated. Use :data:`sys.last_exc` instead.
(Contributed by Irit Katriel in :gh:`102778`.)

Pending Removal in Python 3.13
------------------------------

Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(kw2)
STRUCT_FOR_ID(lambda)
STRUCT_FOR_ID(last)
STRUCT_FOR_ID(last_exc)
STRUCT_FOR_ID(last_node)
STRUCT_FOR_ID(last_traceback)
STRUCT_FOR_ID(last_type)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Lib/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def showsyntaxerror(self, filename=None):
"""
type, value, tb = sys.exc_info()
sys.last_exc = value
sys.last_type = type
sys.last_value = value
sys.last_traceback = tb
Expand All @@ -119,7 +120,7 @@ def showsyntaxerror(self, filename=None):
else:
# Stuff in the right filename
value = SyntaxError(msg, (filename, lineno, offset, line))
sys.last_value = value
sys.last_exc = sys.last_value = value
if sys.excepthook is sys.__excepthook__:
lines = traceback.format_exception_only(type, value)
self.write(''.join(lines))
Expand All @@ -138,6 +139,7 @@ def showtraceback(self):
"""
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
sys.last_traceback = last_tb
sys.last_exc = ei[1]
try:
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
if sys.excepthook is sys.__excepthook__:
Expand Down
5 changes: 4 additions & 1 deletion Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ def distb(tb=None, *, file=None, show_caches=False, adaptive=False):
"""Disassemble a traceback (default: last traceback)."""
if tb is None:
try:
tb = sys.last_traceback
if hasattr(sys, 'last_exc'):
tb = sys.last_exc.__traceback__
else:
tb = sys.last_traceback
except AttributeError:
raise RuntimeError("no last traceback to disassemble") from None
while tb.tb_next: tb = tb.tb_next
Expand Down
3 changes: 2 additions & 1 deletion Lib/idlelib/idle_test/test_stackviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def setUpClass(cls):
except NameError:
svs.last_type, svs.last_value, svs.last_traceback = (
sys.exc_info())
svs.last_exc = svs.last_value

requires('gui')
cls.root = Tk()
Expand All @@ -27,7 +28,7 @@ def setUpClass(cls):
@classmethod
def tearDownClass(cls):
svs = stackviewer.sys
del svs.last_traceback, svs.last_type, svs.last_value
del svs.last_exc, svs.last_traceback, svs.last_type, svs.last_value

cls.root.update_idletasks()
## for id in cls.root.tk.call('after', 'info'):
Expand Down
7 changes: 5 additions & 2 deletions Lib/idlelib/pyshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -1367,11 +1367,14 @@ def open_stack_viewer(self, event=None):
if self.interp.rpcclt:
return self.interp.remote_stack_viewer()
try:
sys.last_traceback
if hasattr(sys, 'last_exc'):
sys.last_exc.__traceback__
else:
sys.last_traceback
except:
messagebox.showerror("No stack trace",
"There is no stack trace yet.\n"
"(sys.last_traceback is not defined)",
"(sys.last_exc and sys.last_traceback are not defined)",
parent=self.text)
return
from idlelib.stackviewer import StackBrowser
Expand Down
2 changes: 2 additions & 0 deletions Lib/idlelib/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ def print_exception():
efile = sys.stderr
typ, val, tb = excinfo = sys.exc_info()
sys.last_type, sys.last_value, sys.last_traceback = excinfo
sys.last_exc = val
seen = set()

def print_exc(typ, exc, tb):
Expand Down Expand Up @@ -629,6 +630,7 @@ def stackviewer(self, flist_oid=None):
flist = self.rpchandler.get_remote_proxy(flist_oid)
while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
tb = tb.tb_next
sys.last_exc = val
sys.last_type = typ
sys.last_value = val
item = stackviewer.StackTreeItem(flist, tb)
Expand Down
21 changes: 15 additions & 6 deletions Lib/idlelib/stackviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ def __init__(self, flist=None, tb=None):

def get_stack(self, tb):
if tb is None:
tb = sys.last_traceback
if hasattr(sys, 'last_exc'):
tb = sys.last_exc.__traceback__
else:
tb = sys.last_traceback
stack = []
if tb and tb.tb_frame is None:
tb = tb.tb_next
Expand All @@ -37,11 +40,15 @@ def get_stack(self, tb):
return stack

def get_exception(self):
type = sys.last_type
value = sys.last_value
if hasattr(type, "__name__"):
type = type.__name__
s = str(type)
if hasattr(sys, 'last_exc'):
typ = type(sys.last_exc)
value = sys.last_exc
else:
typ = sys.last_type
value = sys.last_value
if hasattr(typ, "__name__"):
typ = typ.__name__
s = str(typ)
if value is not None:
s = s + ": " + str(value)
return s
Expand Down Expand Up @@ -136,13 +143,15 @@ def _stack_viewer(parent): # htest #
except NameError:
exc_type, exc_value, exc_tb = sys.exc_info()
# inject stack trace to sys
sys.last_exc = exc_value
sys.last_type = exc_type
sys.last_value = exc_value
sys.last_traceback = exc_tb

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

# restore sys to original state
del sys.last_exc
del sys.last_type
del sys.last_value
del sys.last_traceback
Expand Down
6 changes: 5 additions & 1 deletion Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1739,7 +1739,11 @@ def post_mortem(t=None):

def pm():
"""Enter post-mortem debugging of the traceback found in sys.last_traceback."""
post_mortem(sys.last_traceback)
if hasattr(sys, 'last_exc'):
tb = sys.last_exc.__traceback__
else:
tb = sys.last_traceback
post_mortem(tb)


# Main program for testing
Expand Down
4 changes: 2 additions & 2 deletions Lib/pydoc_data/topics.py
Original file line number Diff line number Diff line change
Expand Up @@ -4799,7 +4799,7 @@
'pdb.pm()\n'
'\n'
' Enter post-mortem debugging of the traceback found in\n'
' "sys.last_traceback".\n'
' "sys.last_exc".\n'
'\n'
'The "run*" functions and "set_trace()" are aliases for '
'instantiating\n'
Expand Down Expand Up @@ -13858,7 +13858,7 @@
'if\n'
' the interpreter is interactive, it is also made available to '
'the\n'
' user as "sys.last_traceback".\n'
' user as "sys.last_exc".\n'
'\n'
' For explicitly created tracebacks, it is up to the creator '
'of\n'
Expand Down
10 changes: 9 additions & 1 deletion Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,10 @@ def test_disassemble_try_finally(self):
self.do_disassembly_test(_tryfinallyconst, dis_tryfinallyconst)

def test_dis_none(self):
try:
del sys.last_exc
except AttributeError:
pass
try:
del sys.last_traceback
except AttributeError:
Expand All @@ -1043,7 +1047,7 @@ def test_dis_traceback(self):
1/0
except Exception as e:
tb = e.__traceback__
sys.last_traceback = tb
sys.last_exc = e

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

class TestDisTraceback(DisTestBase):
def setUp(self) -> None:
try: # We need to clean up existing tracebacks
del sys.last_exc
except AttributeError:
pass
try: # We need to clean up existing tracebacks
del sys.last_traceback
except AttributeError:
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_ttk/test_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ def test_widget_destroy(self):
# value which causes the tracing callback to be called and then
# it tries calling instance attributes not yet defined.
ttk.LabeledScale(self.root, variable=myvar)
if hasattr(sys, 'last_type'):
if hasattr(sys, 'last_exc'):
self.assertNotEqual(type(sys.last_exc), tkinter.TclError)
elif hasattr(sys, 'last_type'):
self.assertNotEqual(sys.last_type, tkinter.TclError)

def test_initialization(self):
Expand Down
1 change: 1 addition & 0 deletions Lib/tkinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2400,6 +2400,7 @@ def report_callback_exception(self, exc, val, tb):
should when sys.stderr is None."""
import traceback
print("Exception in Tkinter callback", file=sys.stderr)
sys.last_exc = val
sys.last_type = exc
sys.last_value = val
sys.last_traceback = tb
Expand Down
16 changes: 10 additions & 6 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,20 +179,24 @@ def _safe_string(value, what, func=str):
# --

def print_exc(limit=None, file=None, chain=True):
"""Shorthand for 'print_exception(*sys.exc_info(), limit, file)'."""
"""Shorthand for 'print_exception(*sys.exc_info(), limit, file, chain)'."""
print_exception(*sys.exc_info(), limit=limit, file=file, chain=chain)

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

def print_last(limit=None, file=None, chain=True):
"""This is a shorthand for 'print_exception(sys.last_type,
sys.last_value, sys.last_traceback, limit, file)'."""
if not hasattr(sys, "last_type"):
"""This is a shorthand for 'print_exception(sys.last_exc, limit, file, chain)'."""
if not hasattr(sys, "last_exc") and not hasattr(sys, "last_type"):
raise ValueError("no last exception")
print_exception(sys.last_type, sys.last_value, sys.last_traceback,
limit, file, chain)

if hasattr(sys, "last_exc"):
print_exception(sys.last_exc, limit, file, chain)
else:
print_exception(sys.last_type, sys.last_value, sys.last_traceback,
limit, file, chain)


#
# Printing and Extracting Stacks.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :data:`sys.last_exc` and deprecate :data:`sys.last_type`, :data:`sys.last_value`
and :data:`sys.last_traceback`,
which hold the same information in its legacy form.
2 changes: 1 addition & 1 deletion Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -1304,7 +1304,7 @@ finalize_modules_delete_special(PyThreadState *tstate, int verbose)
{
// List of names to clear in sys
static const char * const sys_deletes[] = {
"path", "argv", "ps1", "ps2",
"path", "argv", "ps1", "ps2", "last_exc",
"last_type", "last_value", "last_traceback",
"__interactivehook__",
// path_hooks and path_importer_cache are cleared
Expand Down
4 changes: 4 additions & 0 deletions Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,10 @@ _PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars)
}

if (set_sys_last_vars) {
if (_PySys_SetAttr(&_Py_ID(last_exc), exc) < 0) {
_PyErr_Clear(tstate);
}
/* Legacy version: */
if (_PySys_SetAttr(&_Py_ID(last_type), typ) < 0) {
_PyErr_Clear(tstate);
}
Expand Down
6 changes: 4 additions & 2 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2670,11 +2670,13 @@ stderr -- standard error object; used for error messages\n\
By assigning other file objects (or objects that behave like files)\n\
to these, it is possible to redirect all of the interpreter's I/O.\n\
\n\
last_exc - the last uncaught exception\n\
Only available in an interactive session after a\n\
traceback has been printed.\n\
last_type -- type of last uncaught exception\n\
last_value -- value of last uncaught exception\n\
last_traceback -- traceback of last uncaught exception\n\
These three are only available in an interactive session after a\n\
traceback has been printed.\n\
These three are the (deprecated) legacy representation of last_exc.\n\
"
)
/* concatenating string here */
Expand Down

0 comments on commit bb6764d

Please sign in to comment.