From 3a125051cf0c739912ae38de74db40b48511ded9 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Wed, 5 Jan 2022 11:08:32 +0000 Subject: [PATCH 1/6] added sys.exception() --- Doc/library/sys.rst | 45 ++++++++++++++++++++++++------------- Doc/tutorial/errors.rst | 2 +- Lib/test/test_sys.py | 20 +++++++++++++++++ Python/clinic/sysmodule.c.h | 24 +++++++++++++++++++- Python/sysmodule.c | 24 ++++++++++++++++++++ 5 files changed, 98 insertions(+), 17 deletions(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 7d1b21f05edb19..bfed26f0adcda1 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -378,26 +378,41 @@ always available. .. versionadded:: 3.8 __unraisablehook__ + +.. function:: exception() + + This function returns the exception that is currently being handled. This + exception is specific both to the current thread and to the current stack + frame. If the current stack frame is not handling an exception, the + exception is taken from the calling stack frame, or its caller, and so on + until a stack frame is found that is handling an exception. Here, + "handling an exception" is defined as "executing an except clause." + For any stack frame, only the exception being currently handled is + accessible. + + .. index:: object: traceback + + If no exception is being handled anywhere on the stack, ``None`` is + returned. + + .. versionadded: 3.11 + + .. function:: exc_info() - This function returns a tuple of three values that give information about the - exception that is currently being handled. The information returned is specific - both to the current thread and to the current stack frame. If the current stack - frame is not handling an exception, the information is taken from the calling - stack frame, or its caller, and so on until a stack frame is found that is - handling an exception. Here, "handling an exception" is defined as "executing - an except clause." For any stack frame, only information about the exception - being currently handled is accessible. + This function returns the old-style representation of the handled + exception. If an exception ``e`` is currently handled (so + :func:`exception` would return ``e``), :func:`exc_info` returns the + tuple ``(type(e), e, e.__traceback__)``. + That is, a tuple containing the type of the exception (a subclass of + :exc:`BaseException`), the exception itself, and a :ref:`traceback + object ` which typically encapsulates the call + stack at the point where the exception last occurred. .. index:: object: traceback - If no exception is being handled anywhere on the stack, a tuple containing - three ``None`` values is returned. Otherwise, the values returned are - ``(type, value, traceback)``. Their meaning is: *type* gets the type of the - exception being handled (a subclass of :exc:`BaseException`); *value* gets - the exception instance (an instance of the exception type); *traceback* gets - a :ref:`traceback object ` which typically encapsulates - the call stack at the point where the exception last occurred. + If no exception is being handled anywhere on the stack, this function + return a tuple containing three ``None`` values. .. versionchanged:: 3.11 The ``type`` and ``traceback`` fields are now derived from the ``value`` diff --git a/Doc/tutorial/errors.rst b/Doc/tutorial/errors.rst index f2490d65db5d49..e4b9b204d3192b 100644 --- a/Doc/tutorial/errors.rst +++ b/Doc/tutorial/errors.rst @@ -167,7 +167,7 @@ then re-raise the exception (allowing a caller to handle the exception as well): raise Alternatively the last except clause may omit the exception name(s), however the exception -value must then be retrieved from ``sys.exc_info()[1]``. +value must then be retrieved with ``sys.exception()``. The :keyword:`try` ... :keyword:`except` statement has an optional *else clause*, which, when present, must follow all *except clauses*. It is useful diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 96075cf3b3473c..68b801643a3620 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -71,6 +71,26 @@ def baddisplayhook(obj): code = compile("42", "", "single") self.assertRaises(ValueError, eval, code) +class ExcInfoTest(unittest.TestCase): + def test_exc_info_no_exception(self): + self.assertEqual(sys.exc_info(), (None, None, None)) + self.assertEqual(sys.exception(), None) + + def test_exc_info_with_exception(self): + def f(): + raise ValueError(42) + + try: + f() + except Exception as e_: + e = e_ + exc_info = sys.exc_info() + exc = sys.exception() + + self.assertIs(exc, e) + self.assertIs(exc_info[0], type(e)) + self.assertIs(exc_info[1], e) + self.assertIs(exc_info[2], e.__traceback__) class ExceptHookTest(unittest.TestCase): diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index 8350fbf98561a9..ce5390c8a1e588 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -76,6 +76,28 @@ sys_excepthook(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } +PyDoc_STRVAR(sys_exception__doc__, +"exception($module, /)\n" +"--\n" +"\n" +"Return the current exception.\n" +"\n" +"Return the most recent exception caught by an except clause\n" +"in the current stack frame or in an older stack frame, or None\n" +"if no such exception exists."); + +#define SYS_EXCEPTION_METHODDEF \ + {"exception", (PyCFunction)sys_exception, METH_NOARGS, sys_exception__doc__}, + +static PyObject * +sys_exception_impl(PyObject *module); + +static PyObject * +sys_exception(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return sys_exception_impl(module); +} + PyDoc_STRVAR(sys_exc_info__doc__, "exc_info($module, /)\n" "--\n" @@ -992,4 +1014,4 @@ sys_getandroidapilevel(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=855fc93b2347710b input=a9049054013a1b77]*/ +/*[clinic end generated code: output=60756bc6f683e0c8 input=a9049054013a1b77]*/ diff --git a/Python/sysmodule.c b/Python/sysmodule.c index f912115560704c..31b370710e4c55 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -771,6 +771,28 @@ sys_excepthook_impl(PyObject *module, PyObject *exctype, PyObject *value, } +/*[clinic input] +sys.exception + +Return the current exception. + +Return the most recent exception caught by an except clause +in the current stack frame or in an older stack frame, or None +if no such exception exists. +[clinic start generated code]*/ + +static PyObject * +sys_exception_impl(PyObject *module) +/*[clinic end generated code: output=2381ee2f25953e40 input=c88fbb94b6287431]*/ +{ + _PyErr_StackItem *err_info = _PyErr_GetTopmostException(_PyThreadState_GET()); + if (err_info->exc_value != NULL) { + return Py_NewRef(err_info->exc_value); + } + Py_RETURN_NONE; +} + + /*[clinic input] sys.exc_info @@ -1963,6 +1985,7 @@ static PyMethodDef sys_methods[] = { SYS__CURRENT_FRAMES_METHODDEF SYS__CURRENT_EXCEPTIONS_METHODDEF SYS_DISPLAYHOOK_METHODDEF + SYS_EXCEPTION_METHODDEF SYS_EXC_INFO_METHODDEF SYS_EXCEPTHOOK_METHODDEF SYS_EXIT_METHODDEF @@ -2457,6 +2480,7 @@ Functions:\n\ \n\ displayhook() -- print an object to the screen, and save it in builtins._\n\ excepthook() -- print an exception and its traceback to sys.stderr\n\ +exception() -- return the current exception (thread safe)\n\ exc_info() -- return thread-safe information about the current exception\n\ exit() -- exit the interpreter by raising SystemExit\n\ getdlopenflags() -- returns flags to be used for dlopen() calls\n\ From e0805c4272d54cfd2d4f8fb0f9b2406958092528 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 11:53:16 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2022-01-10-11-53-15.bpo-46328.6i9Wvq.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-01-10-11-53-15.bpo-46328.6i9Wvq.rst diff --git a/Misc/NEWS.d/next/Library/2022-01-10-11-53-15.bpo-46328.6i9Wvq.rst b/Misc/NEWS.d/next/Library/2022-01-10-11-53-15.bpo-46328.6i9Wvq.rst new file mode 100644 index 00000000000000..fec790d52cef33 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-10-11-53-15.bpo-46328.6i9Wvq.rst @@ -0,0 +1 @@ +Added the :meth:`sys.exception` method which returns the active exception instance. \ No newline at end of file From dfc677791224696823c89ed294d798272e419cad Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 10 Jan 2022 13:28:58 +0000 Subject: [PATCH 3/6] exception --> exception instance --- Doc/library/sys.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index bfed26f0adcda1..ed5dbf8961ebbd 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -381,14 +381,14 @@ always available. .. function:: exception() - This function returns the exception that is currently being handled. This - exception is specific both to the current thread and to the current stack - frame. If the current stack frame is not handling an exception, the - exception is taken from the calling stack frame, or its caller, and so on - until a stack frame is found that is handling an exception. Here, - "handling an exception" is defined as "executing an except clause." - For any stack frame, only the exception being currently handled is - accessible. + This function returns the exception instance that is currently being + handled. This exception is specific both to the current thread and + to the current stack frame. If the current stack frame is not handling + an exception, the exception is taken from the calling stack frame, or its + caller, and so on until a stack frame is found that is handling an + exception. Here, "handling an exception" is defined as "executing an + except clause." For any stack frame, only the exception being currently + handled is accessible. .. index:: object: traceback From 1525389d7efd44b426ae5d72838e1da9161122b0 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Mon, 10 Jan 2022 13:48:56 +0000 Subject: [PATCH 4/6] fix doc build --- Doc/library/sys.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index ed5dbf8961ebbd..5e47201f88eae1 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -395,7 +395,7 @@ always available. If no exception is being handled anywhere on the stack, ``None`` is returned. - .. versionadded: 3.11 + .. versionadded:: 3.11 .. function:: exc_info() From e660f0700a8dabab242b796a591d597e75b8a836 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Wed, 12 Jan 2022 22:27:00 +0000 Subject: [PATCH 5/6] more test cases. Add whatnew entry --- Doc/whatsnew/3.11.rst | 3 +++ Lib/test/test_sys.py | 53 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index faa63a93895a20..c04b35609b4a82 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -301,6 +301,9 @@ sys the results of subsequent calls to :func:`exc_info`. (Contributed by Irit Katriel in :issue:`45711`.) +* Add :func:`sys.exception` which returns the active exception instance + (equivalent to ``sys.exc_info()[1]``). + (Contributed by Irit Katriel in :issue:`46328`.) threading --------- diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 68b801643a3620..a38060673ece71 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -71,12 +71,14 @@ def baddisplayhook(obj): code = compile("42", "", "single") self.assertRaises(ValueError, eval, code) -class ExcInfoTest(unittest.TestCase): +class ActiveExceptionTests(unittest.TestCase): def test_exc_info_no_exception(self): self.assertEqual(sys.exc_info(), (None, None, None)) + + def test_sys_exception_no_exception(self): self.assertEqual(sys.exception(), None) - def test_exc_info_with_exception(self): + def test_exc_info_with_exception_instance(self): def f(): raise ValueError(42) @@ -85,13 +87,54 @@ def f(): except Exception as e_: e = e_ exc_info = sys.exc_info() - exc = sys.exception() - self.assertIs(exc, e) - self.assertIs(exc_info[0], type(e)) + self.assertIsInstance(e, ValueError) + self.assertIs(exc_info[0], ValueError) self.assertIs(exc_info[1], e) self.assertIs(exc_info[2], e.__traceback__) + def test_exc_info_with_exception_type(self): + def f(): + raise ValueError + + try: + f() + except Exception as e_: + e = e_ + exc_info = sys.exc_info() + + self.assertIsInstance(e, ValueError) + self.assertIs(exc_info[0], ValueError) + self.assertIs(exc_info[1], e) + self.assertIs(exc_info[2], e.__traceback__) + + def test_sys_exception_with_exception_instance(self): + def f(): + raise ValueError(42) + + try: + f() + except Exception as e_: + e = e_ + exc = sys.exception() + + self.assertIsInstance(e, ValueError) + self.assertIs(exc, e) + + def test_sys_exception_with_exception_type(self): + def f(): + raise ValueError + + try: + f() + except Exception as e_: + e = e_ + exc = sys.exception() + + self.assertIsInstance(e, ValueError) + self.assertIs(exc, e) + + class ExceptHookTest(unittest.TestCase): def test_original_excepthook(self): From 5bc36e9aa05206e65d8e5231ba439c260634f7fa Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 13 Jan 2022 11:26:35 +0000 Subject: [PATCH 6/6] reword help text re threading --- Python/sysmodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 31b370710e4c55..0b7b61d8b1e281 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2480,8 +2480,8 @@ Functions:\n\ \n\ displayhook() -- print an object to the screen, and save it in builtins._\n\ excepthook() -- print an exception and its traceback to sys.stderr\n\ -exception() -- return the current exception (thread safe)\n\ -exc_info() -- return thread-safe information about the current exception\n\ +exception() -- return the current thread's active exception\n\ +exc_info() -- return information about the current thread's active exception\n\ exit() -- exit the interpreter by raising SystemExit\n\ getdlopenflags() -- returns flags to be used for dlopen() calls\n\ getprofile() -- get the global profiling function\n\