Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gh-102778: Add sys.last_exc, deprecate sys.last_type, sys.last_value,sys.last_traceback #102779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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