| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 2725cb0 commit ef9d9b6
12 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -72,6 +72,9 @@ Printing and clearing | |||
| 72 | 72 | ||
| 73 | 73 | .. c:function:: void PyErr_WriteUnraisable(PyObject *obj) | |
| 74 | 74 | ||
| 75 | + Call :func:`sys.unraisablehook` using the current exception and *obj* | ||
| 76 | + argument. | ||
| 77 | + | ||
| 75 | 78 | This utility function prints a warning message to ``sys.stderr`` when an | |
| 76 | 79 | exception has been set but it is impossible for the interpreter to actually | |
| 77 | 80 | raise the exception. It is used, for example, when an exception occurs in an | |
@@ -81,6 +84,8 @@ Printing and clearing | |||
| 81 | 84 | in which the unraisable exception occurred. If possible, | |
| 82 | 85 | the repr of *obj* will be printed in the warning message. | |
| 83 | 86 | ||
| 87 | + An exception must be set when calling this function. | ||
| 88 | + | ||
| 84 | 89 | ||
| 85 | 90 | Raising exceptions | |
| 86 | 91 | ================== | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -248,16 +248,19 @@ always available. | |||
| 248 | 248 | before the program exits. The handling of such top-level exceptions can be | |
| 249 | 249 | customized by assigning another three-argument function to ``sys.excepthook``. | |
| 250 | 250 | ||
| 251 | + See also :func:`unraisablehook` which handles unraisable exceptions. | ||
| 252 | + | ||
| 251 | 253 | ||
| 252 | 254 | .. data:: __breakpointhook__ | |
| 253 | 255 | __displayhook__ | |
| 254 | 256 | __excepthook__ | |
| 257 | + __unraisablehook__ | ||
| 255 | 258 | ||
| 256 | 259 | These objects contain the original values of ``breakpointhook``, | |
| 257 | - ``displayhook``, and ``excepthook`` at the start of the program. They are | ||
| 258 | - saved so that ``breakpointhook``, ``displayhook`` and ``excepthook`` can be | ||
| 259 | - restored in case they happen to get replaced with broken or alternative | ||
| 260 | - objects. | ||
| 260 | + ``displayhook``, ``excepthook``, and ``unraisablehook`` at the start of the | ||
| 261 | + program. They are saved so that ``breakpointhook``, ``displayhook`` and | ||
| 262 | + ``excepthook``, ``unraisablehook`` can be restored in case they happen to | ||
| 263 | + get replaced with broken or alternative objects. | ||
| 261 | 264 | ||
| 262 | 265 | .. versionadded:: 3.7 | |
| 263 | 266 | __breakpointhook__ | |
@@ -1487,6 +1490,28 @@ always available. | |||
| 1487 | 1490 | is suppressed and only the exception type and value are printed. | |
| 1488 | 1491 | ||
| 1489 | 1492 | ||
| 1493 | + .. function:: unraisablehook(unraisable, /) | ||
| 1494 | + | ||
| 1495 | + Handle an unraisable exception. | ||
| 1496 | + | ||
| 1497 | + Called when an exception has occurred but there is no way for Python to | ||
| 1498 | + handle it. For example, when a destructor raises an exception or during | ||
| 1499 | + garbage collection (:func:`gc.collect`). | ||
| 1500 | + | ||
| 1501 | + The *unraisable* argument has the following attributes: | ||
| 1502 | + | ||
| 1503 | + * *exc_type*: Exception type. | ||
| 1504 | + * *exc_value*: Exception value, can be ``None``. | ||
| 1505 | + * *exc_traceback*: Exception traceback, can be ``None``. | ||
| 1506 | + * *object*: Object causing the exception, can be ``None``. | ||
| 1507 | + | ||
| 1508 | + :func:`sys.unraisablehook` can be overridden to control how unraisable | ||
| 1509 | + exceptions are handled. | ||
| 1510 | + | ||
| 1511 | + See also :func:`excepthook` which handles uncaught exceptions. | ||
| 1512 | + | ||
| 1513 | + .. versionadded:: 3.8 | ||
| 1514 | + | ||
| 1490 | 1515 | .. data:: version | |
| 1491 | 1516 | ||
| 1492 | 1517 | A string containing the version number of the Python interpreter plus additional | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -481,6 +481,16 @@ and manipulating normal distributions of a random variable. | |||
| 481 | 481 | [7.672102882379219, 12.000027119750287, 4.647488369766392] | |
| 482 | 482 | ||
| 483 | 483 | ||
| 484 | + sys | ||
| 485 | + --- | ||
| 486 | + | ||
| 487 | + Add new :func:`sys.unraisablehook` function which can be overridden to control | ||
| 488 | + how "unraisable exceptions" are handled. It is called when an exception has | ||
| 489 | + occurred but there is no way for Python to handle it. For example, when a | ||
| 490 | + destructor raises an exception or during garbage collection | ||
| 491 | + (:func:`gc.collect`). | ||
| 492 | + | ||
| 493 | + | ||
| 484 | 494 | tarfile | |
| 485 | 495 | ------- | |
| 486 | 496 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -48,6 +48,7 @@ extern int _PySys_InitMain( | |||
| 48 | 48 | PyInterpreterState *interp); | |
| 49 | 49 | extern _PyInitError _PyImport_Init(PyInterpreterState *interp); | |
| 50 | 50 | extern _PyInitError _PyExc_Init(void); | |
| 51 | + extern _PyInitError _PyErr_Init(void); | ||
| 51 | 52 | extern _PyInitError _PyBuiltins_AddExceptions(PyObject * bltinmod); | |
| 52 | 53 | extern _PyInitError _PyImportHooks_Init(void); | |
| 53 | 54 | extern int _PyFloat_Init(void); | |
@@ -100,8 +101,11 @@ PyAPI_FUNC(_PyInitError) _Py_PreInitializeFromCoreConfig( | |||
| 100 | 101 | const _PyCoreConfig *coreconfig, | |
| 101 | 102 | const _PyArgv *args); | |
| 102 | 103 | ||
| 104 | + | ||
| 103 | 105 | PyAPI_FUNC(int) _Py_HandleSystemExit(int *exitcode_p); | |
| 104 | 106 | ||
| 107 | + PyAPI_FUNC(PyObject*) _PyErr_WriteUnraisableDefaultHook(PyObject *unraisable); | ||
| 108 | + | ||
| 105 | 109 | #ifdef __cplusplus | |
| 106 | 110 | } | |
| 107 | 111 | #endif | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -876,6 +876,81 @@ def test__enablelegacywindowsfsencoding(self): | |||
| 876 | 876 | self.assertEqual(out, 'mbcs replace') | |
| 877 | 877 | ||
| 878 | 878 | ||
| 879 | + @test.support.cpython_only | ||
| 880 | + class UnraisableHookTest(unittest.TestCase): | ||
| 881 | + def write_unraisable_exc(self, exc, obj): | ||
| 882 | + import _testcapi | ||
| 883 | + import types | ||
| 884 | + try: | ||
| 885 | + # raise the exception to get a traceback in the except block | ||
| 886 | + try: | ||
| 887 | + raise exc | ||
| 888 | + except Exception as exc2: | ||
| 889 | + _testcapi.write_unraisable_exc(exc2, obj) | ||
| 890 | + return types.SimpleNamespace(exc_type=type(exc2), | ||
| 891 | + exc_value=exc2, | ||
| 892 | + exc_traceback=exc2.__traceback__, | ||
| 893 | + object=obj) | ||
| 894 | + finally: | ||
| 895 | + # Explicitly break any reference cycle | ||
| 896 | + exc = None | ||
| 897 | + exc2 = None | ||
| 898 | + | ||
| 899 | + def test_original_unraisablehook(self): | ||
| 900 | + obj = "an object" | ||
| 901 | + | ||
| 902 | + with test.support.captured_output("stderr") as stderr: | ||
| 903 | + with test.support.swap_attr(sys, 'unraisablehook', | ||
| 904 | + sys.__unraisablehook__): | ||
| 905 | + self.write_unraisable_exc(ValueError(42), obj) | ||
| 906 | + | ||
| 907 | + err = stderr.getvalue() | ||
| 908 | + self.assertIn(f'Exception ignored in: {obj!r}\n', err) | ||
| 909 | + self.assertIn('Traceback (most recent call last):\n', err) | ||
| 910 | + self.assertIn('ValueError: 42\n', err) | ||
| 911 | + | ||
| 912 | + def test_original_unraisablehook_wrong_type(self): | ||
| 913 | + exc = ValueError(42) | ||
| 914 | + with test.support.swap_attr(sys, 'unraisablehook', | ||
| 915 | + sys.__unraisablehook__): | ||
| 916 | + with self.assertRaises(TypeError): | ||
| 917 | + sys.unraisablehook(exc) | ||
| 918 | + | ||
| 919 | + def test_custom_unraisablehook(self): | ||
| 920 | + hook_args = None | ||
| 921 | + | ||
| 922 | + def hook_func(args): | ||
| 923 | + nonlocal hook_args | ||
| 924 | + hook_args = args | ||
| 925 | + | ||
| 926 | + obj = object() | ||
| 927 | + try: | ||
| 928 | + with test.support.swap_attr(sys, 'unraisablehook', hook_func): | ||
| 929 | + expected = self.write_unraisable_exc(ValueError(42), obj) | ||
| 930 | + for attr in "exc_type exc_value exc_traceback object".split(): | ||
| 931 | + self.assertEqual(getattr(hook_args, attr), | ||
| 932 | + getattr(expected, attr), | ||
| 933 | + (hook_args, expected)) | ||
| 934 | + finally: | ||
| 935 | + # expected and hook_args contain an exception: break reference cycle | ||
| 936 | + expected = None | ||
| 937 | + hook_args = None | ||
| 938 | + | ||
| 939 | + def test_custom_unraisablehook_fail(self): | ||
| 940 | + def hook_func(*args): | ||
| 941 | + raise Exception("hook_func failed") | ||
| 942 | + | ||
| 943 | + with test.support.captured_output("stderr") as stderr: | ||
| 944 | + with test.support.swap_attr(sys, 'unraisablehook', hook_func): | ||
| 945 | + self.write_unraisable_exc(ValueError(42), None) | ||
| 946 | + | ||
| 947 | + err = stderr.getvalue() | ||
| 948 | + self.assertIn(f'Exception ignored in: {hook_func!r}\n', | ||
| 949 | + err) | ||
| 950 | + self.assertIn('Traceback (most recent call last):\n', err) | ||
| 951 | + self.assertIn('Exception: hook_func failed\n', err) | ||
| 952 | + | ||
| 953 | + | ||
| 879 | 954 | @test.support.cpython_only | |
| 880 | 955 | class SizeofTest(unittest.TestCase): | |
| 881 | 956 | ||
@@ -1277,8 +1352,5 @@ def test_asyncgen_hooks(self): | |||
| 1277 | 1352 | self.assertIsNone(cur.finalizer) | |
| 1278 | 1353 | ||
| 1279 | 1354 | ||
| 1280 | - def test_main(): | ||
| 1281 | - test.support.run_unittest(SysModuleTest, SizeofTest) | ||
| 1282 | - | ||
| 1283 | 1355 | if __name__ == "__main__": | |
| 1284 | - test_main() | ||
| 1356 | + unittest.main() | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,5 @@ | |||
| 1 | + Add new :func:`sys.unraisablehook` function which can be overridden to | ||
| 2 | + control how "unraisable exceptions" are handled. It is called when an | ||
| 3 | + exception has occurred but there is no way for Python to handle it. For | ||
| 4 | + example, when a destructor raises an exception or during garbage collection | ||
| 5 | + (:func:`gc.collect`). | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4982,6 +4982,20 @@ negative_refcount(PyObject *self, PyObject *Py_UNUSED(args)) | |||
| 4982 | 4982 | #endif | |
| 4983 | 4983 | ||
| 4984 | 4984 | ||
| 4985 | + static PyObject* | ||
| 4986 | + test_write_unraisable_exc(PyObject *self, PyObject *args) | ||
| 4987 | + { | ||
| 4988 | + PyObject *exc, *obj; | ||
| 4989 | + if (!PyArg_ParseTuple(args, "OO", &exc, &obj)) { | ||
| 4990 | + return NULL; | ||
| 4991 | + } | ||
| 4992 | + | ||
| 4993 | + PyErr_SetObject((PyObject *)Py_TYPE(exc), exc); | ||
| 4994 | + PyErr_WriteUnraisable(obj); | ||
| 4995 | + Py_RETURN_NONE; | ||
| 4996 | + } | ||
| 4997 | + | ||
| 4998 | + | ||
| 4985 | 4999 | static PyMethodDef TestMethods[] = { | |
| 4986 | 5000 | {"raise_exception", raise_exception, METH_VARARGS}, | |
| 4987 | 5001 | {"raise_memoryerror", raise_memoryerror, METH_NOARGS}, | |
@@ -5221,6 +5235,7 @@ static PyMethodDef TestMethods[] = { | |||
| 5221 | 5235 | #ifdef Py_REF_DEBUG | |
| 5222 | 5236 | {"negative_refcount", negative_refcount, METH_NOARGS}, | |
| 5223 | 5237 | #endif | |
| 5238 | + {"write_unraisable_exc", test_write_unraisable_exc, METH_VARARGS}, | ||
| 5224 | 5239 | {NULL, NULL} /* sentinel */ | |
| 5225 | 5240 | }; | |
| 5226 | 5241 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments