]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-102778: Add sys.last_exc, deprecate sys.last_type, sys.last_value,sys.last_traceba...
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>
Sat, 18 Mar 2023 11:47:11 +0000 (11:47 +0000)
committerGitHub <noreply@github.com>
Sat, 18 Mar 2023 11:47:11 +0000 (11:47 +0000)
22 files changed:
Doc/library/sys.rst
Doc/whatsnew/3.12.rst
Include/internal/pycore_global_objects_fini_generated.h
Include/internal/pycore_global_strings.h
Include/internal/pycore_runtime_init_generated.h
Include/internal/pycore_unicodeobject_generated.h
Lib/code.py
Lib/dis.py
Lib/idlelib/idle_test/test_stackviewer.py
Lib/idlelib/pyshell.py
Lib/idlelib/run.py
Lib/idlelib/stackviewer.py
Lib/pdb.py
Lib/pydoc_data/topics.py
Lib/test/test_dis.py
Lib/test/test_ttk/test_extensions.py
Lib/tkinter/__init__.py
Lib/traceback.py
Misc/NEWS.d/next/Core and Builtins/2023-03-17-13-43-34.gh-issue-102778.ANDv8I.rst [new file with mode: 0644]
Python/pylifecycle.c
Python/pythonrun.c
Python/sysmodule.c

index a53d4908783e1545d09b6f21225792702f6d1dbf..b3b9b5e74ac0689ca3f2524fb57d0336fb553e63 100644 (file)
@@ -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
 
index b55b9619fac226736a968dd71c6558c1813ba237..32fec962560ae198a0472b290b2a1ea97d1926f8 100644 (file)
@@ -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
 =============
@@ -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
 ------------------------------
 
index 4b12ae523c32601dda860345ec133c19036aa1f5..14dfd9ea5823ede3e1be54d0e0f8ba0c94734c3a 100644 (file)
@@ -995,6 +995,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(kw2));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(lambda));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last_exc));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last_node));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last_traceback));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last_type));
index 17fb9ffbbf9f11267aefa95bcb6df4c633c00d98..6f430bb25eb8d30596328eb1fd65115bff5c71d1 100644 (file)
@@ -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)
index b240be57369d9df33fa08a15339f6f7e7b51d647..0452c4c61551de1e589cdc2199c2c4a480b8be55 100644 (file)
@@ -987,6 +987,7 @@ extern "C" {
     INIT_ID(kw2), \
     INIT_ID(lambda), \
     INIT_ID(last), \
+    INIT_ID(last_exc), \
     INIT_ID(last_node), \
     INIT_ID(last_traceback), \
     INIT_ID(last_type), \
index fea9b6dbb1a75f0a2794404423d31e6f54bdb0a4..0a8865942e6d5b7d505d962a7348e8bd134a2cf4 100644 (file)
@@ -1296,6 +1296,9 @@ _PyUnicode_InitStaticStrings(void) {
     string = &_Py_ID(last);
     assert(_PyUnicode_CheckConsistency(string, 1));
     PyUnicode_InternInPlace(&string);
+    string = &_Py_ID(last_exc);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    PyUnicode_InternInPlace(&string);
     string = &_Py_ID(last_node);
     assert(_PyUnicode_CheckConsistency(string, 1));
     PyUnicode_InternInPlace(&string);
index 76000f8c8b2c1e1c98f8fb4c831c2ea3e2de268d..2bd5fa3e795a61975de291264da7d066da5d6da0 100644 (file)
@@ -106,6 +106,7 @@ class InteractiveInterpreter:
 
         """
         type, value, tb = sys.exc_info()
+        sys.last_exc = value
         sys.last_type = type
         sys.last_value = value
         sys.last_traceback = tb
@@ -119,7 +120,7 @@ class InteractiveInterpreter:
             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))
@@ -138,6 +139,7 @@ class InteractiveInterpreter:
         """
         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__:
index 9edde6ae8258dac78152d8dc655595d2b449f78e..c3d152b4de046946f41a538ee9a9e6d975797fd5 100644 (file)
@@ -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
index 98f53f9537bb25727b8d61da5343d0ecd1d7f65d..f4626bb1702a307eea9003d96a2ca8e8d0280415 100644 (file)
@@ -19,6 +19,7 @@ class StackBrowserTest(unittest.TestCase):
         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()
@@ -27,7 +28,7 @@ class StackBrowserTest(unittest.TestCase):
     @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'):
index e68233a5a4131e42fa8ace277704147c7fea73d2..edc77ff26f62f7894bdb5ffc2f9e36fd77676f10 100755 (executable)
@@ -1367,11 +1367,14 @@ class PyShell(OutputWindow):
         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
index 577c49eb67b20d1f83095b3224d341cd88ad1656..6a7b50cf5cfb27001b2c863ad122a70429d5c58b 100644 (file)
@@ -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):
@@ -629,6 +630,7 @@ class Executive:
             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)
index 94ffb4eff4dd26e8e20298100bd886884c534b91..702fd32ca5d1bdf8bef53ef64daa7c389bef30c1 100644 (file)
@@ -27,7 +27,10 @@ class StackTreeItem(TreeItem):
 
     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
@@ -37,11 +40,15 @@ class StackTreeItem(TreeItem):
         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
@@ -136,6 +143,7 @@ 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
@@ -143,6 +151,7 @@ def _stack_viewer(parent):  # htest #
     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
index f11fc55536810f83de3cba74a6778b1da648ee3f..3543f53282db15e393e74efc60de54211319fc95 100755 (executable)
@@ -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
index 573065b4b714d9bb6bec47f878442e9820cb991c..ad1b6aca6b95bc9d32b502142cfebfaa6ff00df3 100644 (file)
@@ -4799,7 +4799,7 @@ topics = {'assert': 'The "assert" statement\n'
              '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'
@@ -13858,7 +13858,7 @@ topics = {'assert': 'The "assert" statement\n'
           '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'
index b77e3b06d0c1f1e01a33914c38017ebc8b40b39f..fa1de1c7ded1b397f5357231a10678b38172f70c 100644 (file)
@@ -1026,6 +1026,10 @@ class DisTests(DisTestBase):
         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:
@@ -1043,7 +1047,7 @@ class DisTests(DisTestBase):
             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)
@@ -1900,6 +1904,10 @@ class TestFinderMethods(unittest.TestCase):
 
 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:
index 6135c49701f08e3955bc185b23e9623aeea8e38f..d5e069716971fe06d13e5915929c58d0799cb1b8 100644 (file)
@@ -45,7 +45,9 @@ class LabeledScaleTest(AbstractTkTest, unittest.TestCase):
         # 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):
index 7565e0f7e46073e156597f0807627e7671ce6ea4..479daf0e5abfc38910ca241ca4884e0bc8d7c8e7 100644 (file)
@@ -2400,6 +2400,7 @@ class Tk(Misc, Wm):
         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
index c43c4720ae5a15a27d04f5f890d2bd4fa7839f1c..9e720ac9948fcea6d25735e06c11e17b6d4d1be4 100644 (file)
@@ -179,7 +179,7 @@ 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):
@@ -187,12 +187,16 @@ def format_exc(limit=None, chain=True):
     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.
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-03-17-13-43-34.gh-issue-102778.ANDv8I.rst b/Misc/NEWS.d/next/Core and Builtins/2023-03-17-13-43-34.gh-issue-102778.ANDv8I.rst
new file mode 100644 (file)
index 0000000..b5da227
--- /dev/null
@@ -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.
index d0c65cc1f7fd44b1864350c317f30839eea532aa..7bf12271db23233c0d7638a94521414d6ff41ad3 100644 (file)
@@ -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
index 07d119a67847c6b05ca6d90e87acb5d51dc579d7..6ea185a1b75bc9bb4d9b5b5bd996ea5a9b8ad650 100644 (file)
@@ -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);
         }
index cc5b9a6d418bfa3a5beeb6d3f17adc025dc5c9fc..126b7d422e00097d3c5b9157f727f0abd6c1ce00 100644 (file)
@@ -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 */