From 33a859b5aff6cac267b5375c61888f6f6dd84adb Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Tue, 17 Aug 2021 18:44:51 +0100 Subject: [PATCH 1/7] bpo-44938: Expose PyErr_ChainExceptions in the stable API --- Doc/c-api/exceptions.rst | 35 +++++++++++++++++++ Doc/whatsnew/3.11.rst | 5 +++ Include/cpython/pyerrors.h | 2 -- Include/pyerrors.h | 3 ++ .../2021-08-17-18-45-25.bpo-44938.CmJPZ3.rst | 2 ++ Modules/_asynciomodule.c | 2 +- Modules/_io/_iomodule.c | 2 +- Modules/_io/bufferedio.c | 4 +-- Modules/_io/fileio.c | 2 +- Modules/_io/iobase.c | 2 +- Modules/_io/textio.c | 4 +-- Modules/_io/winconsoleio.c | 2 +- Modules/_ssl.c | 2 +- Modules/_zoneinfo.c | 2 +- Objects/odictobject.c | 2 +- Objects/weakrefobject.c | 2 +- Python/ceval.c | 2 +- Python/errors.c | 4 +-- Python/import.c | 2 +- Python/traceback.c | 4 +-- 20 files changed, 64 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2021-08-17-18-45-25.bpo-44938.CmJPZ3.rst diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 525a773b7c768f2..a59b45d76f4d98c 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -125,6 +125,41 @@ For convenience, some of these functions will always return a .. versionadded:: 3.5 +.. c:function:: void PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) + + Set the provided exception as the cause for the current exception. If there is no exception + currently set (:c:func:`PyErr_Occurred` returns ``0``) this function behaves as like :c:func:`PyErr_Restore`, but if an exception is already set, set the context + associated with it. + + The exception type should be a class. Do not pass an invalid exception type or value. This call + takes away a reference to each object: you must own a reference to each object before the call + and after the call you no longer own these references. + + .. warning:: + + The caller is responsible for ensuring that this call won't create + any cycles in the exception context chain. + + See :ref:`tut-exception-chaining` for more information regarding the semantics of exception + chaining. + + Example usage: + + .. code-block:: c + + PyObject *type, *value, *traceback; + + /* Fetch the original exception */ + PyErr_Fetch(&type, &value, &traceback); + + /* ... code that might produce other errors ... */ + + /* Set the original exception as the cause for the new exception */ + PyErr_ChainExceptions(type, value, traceback); + + + .. versionadded:: 3.11 + .. c:function:: void PyErr_SetNone(PyObject *type) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index c546ec0fc6d9b4c..a0eabf053e0e82f 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -295,6 +295,11 @@ Changes in the Python API C API Changes ============= + +* Add a new :c:func:`PyErr_ChainExceptions` function that allows to chain exceptions + using the C-API. See :ref:`tut-exception-chaining` for more information + regarding the semantics of exception chaining. (Contributed by Pablo Galindo in :issue:`44938`.) + * Add a new :c:func:`PyType_GetName` function to get type's short name. (Contributed by Hai Shi in :issue:`42035`.) diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h index a3ec5afdb7c7885..6e9264df2cdf64a 100644 --- a/Include/cpython/pyerrors.h +++ b/Include/cpython/pyerrors.h @@ -89,8 +89,6 @@ PyAPI_FUNC(void) _PyErr_GetExcInfo(PyThreadState *, PyObject **, PyObject **, Py /* Context manipulation (PEP 3134) */ -PyAPI_FUNC(void) _PyErr_ChainExceptions(PyObject *, PyObject *, PyObject *); - /* Like PyErr_Format(), but saves current exception as __context__ and __cause__. */ diff --git a/Include/pyerrors.h b/Include/pyerrors.h index f5d1c7115771869..e51538832f61d20 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -22,6 +22,9 @@ PyAPI_FUNC(void) PyErr_Restore(PyObject *, PyObject *, PyObject *); PyAPI_FUNC(void) PyErr_GetExcInfo(PyObject **, PyObject **, PyObject **); PyAPI_FUNC(void) PyErr_SetExcInfo(PyObject *, PyObject *, PyObject *); #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030B0000 +PyAPI_FUNC(void) PyErr_ChainExceptions(PyObject *, PyObject *, PyObject *); +#endif /* Defined in Python/pylifecycle.c diff --git a/Misc/NEWS.d/next/C API/2021-08-17-18-45-25.bpo-44938.CmJPZ3.rst b/Misc/NEWS.d/next/C API/2021-08-17-18-45-25.bpo-44938.CmJPZ3.rst new file mode 100644 index 000000000000000..1d130bc2a409a82 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2021-08-17-18-45-25.bpo-44938.CmJPZ3.rst @@ -0,0 +1,2 @@ +Expose :c:func:`PyErr_ChainExceptions` in the stable API. Patch by Pablo +Galindo. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index ecc73d1ca8bf0f5..e3c66281e1f4523 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -2955,7 +2955,7 @@ task_step(TaskObj *task, PyObject *exc) PyObject *et, *ev, *tb; PyErr_Fetch(&et, &ev, &tb); leave_task(task->task_loop, (PyObject*)task); - _PyErr_ChainExceptions(et, ev, tb); + PyErr_ChainExceptions(et, ev, tb); return NULL; } else { diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c index 170dea41e8abdd2..4d1d44180d805c3 100644 --- a/Modules/_io/_iomodule.c +++ b/Modules/_io/_iomodule.c @@ -497,7 +497,7 @@ _io_open_impl(PyObject *module, PyObject *file, const char *mode, PyObject *exc, *val, *tb, *close_result; PyErr_Fetch(&exc, &val, &tb); close_result = _PyObject_CallMethodIdNoArgs(result, &PyId_close); - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); Py_XDECREF(close_result); Py_DECREF(result); } diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 5984d34cc08290c..2a7233ac8914f9e 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -526,7 +526,7 @@ buffered_close(buffered *self, PyObject *args) } if (exc != NULL) { - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); Py_CLEAR(res); } @@ -2239,7 +2239,7 @@ bufferedrwpair_close(rwpair *self, PyObject *Py_UNUSED(ignored)) Py_DECREF(ret); ret = _forward_call(self->reader, &PyId_close, NULL); if (exc != NULL) { - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); Py_CLEAR(ret); } return ret; diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index b9856b3b6316570..4dcabcbe470971f 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -163,7 +163,7 @@ _io_FileIO_close_impl(fileio *self) } rc = internal_close(self); if (res == NULL) - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); if (rc < 0) Py_CLEAR(res); return res; diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c index 5b687b78176e8c5..85df6ae89fb53c8 100644 --- a/Modules/_io/iobase.c +++ b/Modules/_io/iobase.c @@ -240,7 +240,7 @@ _io__IOBase_close_impl(PyObject *self) PyErr_Fetch(&exc, &val, &tb); rc = _PyObject_SetAttrId(self, &PyId___IOBase_closed, Py_True); - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); if (rc < 0) { Py_CLEAR(res); } diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index eb05ae1a16eb03a..2350696ca4399e3 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -2863,7 +2863,7 @@ _io_TextIOWrapper_tell_impl(textio *self) PyObject *type, *value, *traceback; PyErr_Fetch(&type, &value, &traceback); res = _PyObject_CallMethodIdOneArg(self->decoder, &PyId_setstate, saved_state); - _PyErr_ChainExceptions(type, value, traceback); + PyErr_ChainExceptions(type, value, traceback); Py_DECREF(saved_state); Py_XDECREF(res); } @@ -3078,7 +3078,7 @@ _io_TextIOWrapper_close_impl(textio *self) res = _PyObject_CallMethodIdNoArgs(self->buffer, &PyId_close); if (exc != NULL) { - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); Py_CLEAR(res); } return res; diff --git a/Modules/_io/winconsoleio.c b/Modules/_io/winconsoleio.c index 460f2d3fa071a89..c29696b27dbbb7e 100644 --- a/Modules/_io/winconsoleio.c +++ b/Modules/_io/winconsoleio.c @@ -206,7 +206,7 @@ _io__WindowsConsoleIO_close_impl(winconsoleio *self) PyErr_Fetch(&exc, &val, &tb); rc = internal_close(self); if (res == NULL) - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); if (rc < 0) Py_CLEAR(res); return res; diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 84cc3697b07063b..4cb48d0326de9ad 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -574,7 +574,7 @@ PySSL_ChainExceptions(PySSLSocket *sslsock) { if (sslsock->exc_type == NULL) return 0; - _PyErr_ChainExceptions(sslsock->exc_type, sslsock->exc_value, sslsock->exc_tb); + PyErr_ChainExceptions(sslsock->exc_type, sslsock->exc_value, sslsock->exc_tb); sslsock->exc_type = NULL; sslsock->exc_value = NULL; sslsock->exc_tb = NULL; diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index c722330ee491a9d..dc36d9a1901fcd8 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -228,7 +228,7 @@ zoneinfo_new_instance(PyTypeObject *type, PyObject *key) PyObject *exc, *val, *tb; PyErr_Fetch(&exc, &val, &tb); PyObject *tmp = PyObject_CallMethod(file_obj, "close", NULL); - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); if (tmp == NULL) { Py_CLEAR(self); } diff --git a/Objects/odictobject.c b/Objects/odictobject.c index e5361da6dcdedbd..09a81758183899b 100644 --- a/Objects/odictobject.c +++ b/Objects/odictobject.c @@ -1617,7 +1617,7 @@ _PyODict_SetItem_KnownHash(PyObject *od, PyObject *key, PyObject *value, PyObject *exc, *val, *tb; PyErr_Fetch(&exc, &val, &tb); (void) _PyDict_DelItem_KnownHash(od, key, hash); - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); } } return res; diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index bb56c7dbdb83229..1d5a03e2599ff7e 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -1006,7 +1006,7 @@ PyObject_ClearWeakRefs(PyObject *object) tuple = PyTuple_New(count * 2); if (tuple == NULL) { - _PyErr_ChainExceptions(err_type, err_value, err_tb); + PyErr_ChainExceptions(err_type, err_value, err_tb); return; } diff --git a/Python/ceval.c b/Python/ceval.c index 333c54f50e2e3a1..880e594cf5886e9 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -697,7 +697,7 @@ _Py_FinishPendingCalls(PyThreadState *tstate) PyObject *exc, *val, *tb; _PyErr_Fetch(tstate, &exc, &val, &tb); PyErr_BadInternalCall(); - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); _PyErr_Print(tstate); } } diff --git a/Python/errors.c b/Python/errors.c index ae1cde690eafa50..8c82eef96f8e8fa 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -510,7 +510,7 @@ PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback) The caller is responsible for ensuring that this call won't create any cycles in the exception context chain. */ void -_PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) +PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) { if (exc == NULL) return; @@ -519,7 +519,7 @@ _PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) if (!PyExceptionClass_Check(exc)) { _PyErr_Format(tstate, PyExc_SystemError, - "_PyErr_ChainExceptions: " + "PyErr_ChainExceptions: " "exception %R is not a BaseException subclass", exc); return; diff --git a/Python/import.c b/Python/import.c index 7301fccb9fac0ca..7b1f7d590fba9bb 100644 --- a/Python/import.c +++ b/Python/import.c @@ -655,7 +655,7 @@ remove_module(PyThreadState *tstate, PyObject *name) } } - _PyErr_ChainExceptions(type, value, traceback); + PyErr_ChainExceptions(type, value, traceback); } diff --git a/Python/traceback.c b/Python/traceback.c index 204121ba66d96e2..a3da3e208e25ac4 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -252,7 +252,7 @@ PyTraceBack_Here(PyFrameObject *frame) PyErr_Fetch(&exc, &val, &tb); newtb = _PyTraceBack_FromFrame(tb, frame); if (newtb == NULL) { - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); return -1; } PyErr_Restore(exc, val, newtb); @@ -294,7 +294,7 @@ void _PyTraceback_Add(const char *funcname, const char *filename, int lineno) return; error: - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); } static PyObject * From e9b97242e168b6e54ed045c55e1bc33855e7e797 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Tue, 17 Aug 2021 19:25:02 +0100 Subject: [PATCH 2/7] Regenerate limited abi --- Doc/c-api/exceptions.rst | 6 +++--- Doc/data/stable_abi.dat | 1 + Misc/stable_abi.txt | 2 ++ PC/python3dll.c | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index a59b45d76f4d98c..debb1485d54f9b3 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -133,12 +133,12 @@ For convenience, some of these functions will always return a The exception type should be a class. Do not pass an invalid exception type or value. This call takes away a reference to each object: you must own a reference to each object before the call - and after the call you no longer own these references. - + and after the call you no longer own these references. + .. warning:: The caller is responsible for ensuring that this call won't create - any cycles in the exception context chain. + any cycles in the exception context chain. See :ref:`tut-exception-chaining` for more information regarding the semantics of exception chaining. diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index ea57cc83f7a5109..1ee4051e39ca0a3 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -121,6 +121,7 @@ var,PyEllipsis_Type,3.2, var,PyEnum_Type,3.2, function,PyErr_BadArgument,3.2, function,PyErr_BadInternalCall,3.2, +function,PyErr_ChainExceptions,3.11, function,PyErr_CheckSignals,3.2, function,PyErr_Clear,3.2, function,PyErr_Display,3.2, diff --git a/Misc/stable_abi.txt b/Misc/stable_abi.txt index a3ee0f01beccb93..084dd07c4918cf8 100644 --- a/Misc/stable_abi.txt +++ b/Misc/stable_abi.txt @@ -2149,6 +2149,8 @@ function PyType_GetName added 3.11 function PyType_GetQualName added 3.11 +function PyErr_ChainExceptions + added 3.11 # (Detailed comments aren't really needed for further entries: from here on # we can use version control logs.) diff --git a/PC/python3dll.c b/PC/python3dll.c index 2420e53e7052d5e..0f93140411904e0 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -181,6 +181,7 @@ EXPORT_FUNC(PyDict_Values) EXPORT_FUNC(PyDictProxy_New) EXPORT_FUNC(PyErr_BadArgument) EXPORT_FUNC(PyErr_BadInternalCall) +EXPORT_FUNC(PyErr_ChainExceptions) EXPORT_FUNC(PyErr_CheckSignals) EXPORT_FUNC(PyErr_Clear) EXPORT_FUNC(PyErr_Display) From 8bcee51c8f5d3734e68a978da4a0e9a62e6de639 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 18 Aug 2021 00:23:57 +0100 Subject: [PATCH 3/7] fixup! Regenerate limited abi --- Doc/c-api/exceptions.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index debb1485d54f9b3..991694b120f6510 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -128,12 +128,13 @@ For convenience, some of these functions will always return a .. c:function:: void PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) Set the provided exception as the cause for the current exception. If there is no exception - currently set (:c:func:`PyErr_Occurred` returns ``0``) this function behaves as like :c:func:`PyErr_Restore`, but if an exception is already set, set the context - associated with it. + currently set (:c:func:`PyErr_Occurred` returns ``0``) this function behaves + as like :c:func:`PyErr_Restore`, but if an exception is already set, set the + context associated with it. - The exception type should be a class. Do not pass an invalid exception type or value. This call - takes away a reference to each object: you must own a reference to each object before the call - and after the call you no longer own these references. + The exception type should be a class. Do not pass an invalid exception type or value. This + function steals a reference to each of its arguments in the same fashion as :c:func:`PyErr_Restore` + does. .. warning:: From a6e72545ff37d06157783a0a5b1a651fc3c7cab3 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Wed, 18 Aug 2021 00:33:27 +0100 Subject: [PATCH 4/7] Update Doc/c-api/exceptions.rst Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- Doc/c-api/exceptions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 991694b120f6510..d24d4f3f680eb63 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -155,7 +155,7 @@ For convenience, some of these functions will always return a /* ... code that might produce other errors ... */ - /* Set the original exception as the cause for the new exception */ + /* Set the original exception as the context for the new exception, and set the new exception as the current exception */ PyErr_ChainExceptions(type, value, traceback); From 30135a1b0d4183b9490bed76e6d69324e5305896 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 18 Aug 2021 00:39:08 +0100 Subject: [PATCH 5/7] fixup! Update Doc/c-api/exceptions.rst --- Doc/c-api/exceptions.rst | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index d24d4f3f680eb63..1e90bf68822fc43 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -127,22 +127,24 @@ For convenience, some of these functions will always return a .. c:function:: void PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) - Set the provided exception as the cause for the current exception. If there is no exception - currently set (:c:func:`PyErr_Occurred` returns ``0``) this function behaves - as like :c:func:`PyErr_Restore`, but if an exception is already set, set the - context associated with it. + Set the provided exception as the cause for the current exception. If there + is no exception currently set (:c:func:`PyErr_Occurred` returns ``0``) this + function behaves as like :c:func:`PyErr_Restore`, but if an exception is + already set, set the provided exception as the context (accessible from + Python through the :attr:`__context__` attribute of the exception) of the + one that is currently set. - The exception type should be a class. Do not pass an invalid exception type or value. This - function steals a reference to each of its arguments in the same fashion as :c:func:`PyErr_Restore` - does. + The exception type should be a class. Do not pass an invalid exception type + or value. This function steals a reference to each of its arguments in the + same fashion as :c:func:`PyErr_Restore` does. .. warning:: The caller is responsible for ensuring that this call won't create any cycles in the exception context chain. - See :ref:`tut-exception-chaining` for more information regarding the semantics of exception - chaining. + See :ref:`tut-exception-chaining` for more information regarding the + semantics of exception chaining. Example usage: From 9ffb923b29105a266f2e2c4e80b1e7d2399a6a72 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Wed, 18 Aug 2021 12:01:18 +0100 Subject: [PATCH 6/7] Update Doc/c-api/exceptions.rst Co-authored-by: Petr Viktorin --- Doc/c-api/exceptions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 1e90bf68822fc43..8bbee850fb1b3c8 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -129,7 +129,7 @@ For convenience, some of these functions will always return a Set the provided exception as the cause for the current exception. If there is no exception currently set (:c:func:`PyErr_Occurred` returns ``0``) this - function behaves as like :c:func:`PyErr_Restore`, but if an exception is + function behaves like :c:func:`PyErr_Restore`, but if an exception is already set, set the provided exception as the context (accessible from Python through the :attr:`__context__` attribute of the exception) of the one that is currently set. From a102b8f64206e3196512b3076fd02f472252f27f Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Wed, 18 Aug 2021 12:01:29 +0100 Subject: [PATCH 7/7] Update Doc/c-api/exceptions.rst Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- Doc/c-api/exceptions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 8bbee850fb1b3c8..0c9cda7095454bc 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -127,7 +127,7 @@ For convenience, some of these functions will always return a .. c:function:: void PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) - Set the provided exception as the cause for the current exception. If there + Set the provided exception as the context for the current exception. If there is no exception currently set (:c:func:`PyErr_Occurred` returns ``0``) this function behaves like :c:func:`PyErr_Restore`, but if an exception is already set, set the provided exception as the context (accessible from