Chain exceptions at C level, as already done at Python level.
Python 3 introduced a new killer feature: exceptions are chained bydefault,PEP 3134.
Example:
try:raiseTypeError("err1")exceptTypeError:raiseValueError("err2")
Output:
Traceback(mostrecentcalllast):File"test.py",line2,in<module>raiseTypeError("err1")TypeError:err1Duringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):File"test.py",line4,in<module>raiseValueError("err2")ValueError:err2
Exceptions are chained by default in Python code, but not inextensions written in C.
A new private_PyErr_ChainExceptions() function was introduced inPython 3.4.3 and 3.5 to chain exceptions. Currently, it must be calledexplicitly to chain exceptions and its usage is not trivial.
Example of_PyErr_ChainExceptions() usage from thezipimportmodule to chain the previousOSError to a newZipImportErrorexception:
PyObject*exc,*val,*tb;PyErr_Fetch(&exc,&val,&tb);PyErr_Format(ZipImportError,"can't open Zip file: %R",archive);_PyErr_ChainExceptions(exc,val,tb);
This PEP proposes to also chain exceptions automatically at C level tostay consistent and give more information on failures to helpdebugging. The previous example becomes simply:
PyErr_Format(ZipImportError,"can't open Zip file: %R",archive);
Modify C functions raising exceptions of the Python C API toautomatically chain exceptions: modifyPyErr_SetString(),PyErr_Format(),PyErr_SetNone(), etc.
Keeping the previous exception is not always interesting when the newexception contains information of the previous exception or even moreinformation, especially when the two exceptions have the same type.
Example of an useless exception chain withint(str):
TypeError:abytes-likeobjectisrequired,not'type'Duringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>TypeError:int()argumentmustbeastring,abytes-likeobjectoranumber,not'type'
The newTypeError exception contains more information than theprevious exception. The previous exception should be hidden.
ThePyErr_Clear() function can be called to clear the currentexception before raising a new exception, to not chain the currentexception with a new exception.
Some functions save and then restore the current exception. If a newexception is raised, the exception is currently displayed intosys.stderr or ignored depending on the function. Some of thesefunctions should be modified to chain exceptions instead.
Examples of function ignoring the new exception(s):
ptrace_enter_call(): ignore exceptionsubprocess_fork_exec(): ignore exception raised by enable_gc()t_bootstrap() of the_thread module: ignore exception raisedby trying to display the bootstrap function tosys.stderrPyDict_GetItem(),_PyDict_GetItem_KnownHash(): ignoreexception raised by looking for a key in the dictionary_PyErr_TrySetFromCause(): ignore exceptionPyFrame_LocalsToFast(): ignore exception raised bydict_to_map()_PyObject_Dump(): ignore exception._PyObject_Dump() is usedto debug, to inspect a running process, it should not modify thePython state.Py_ReprLeave(): ignore exception “because there is no way toreport them”type_dealloc(): ignore exception raised byremove_all_subclasses()PyObject_ClearWeakRefs(): ignore exception?call_exc_trace(),call_trace_protected(): ignore exceptionremove_importlib_frames(): ignore exceptiondo_mktuple(), helper used byPy_BuildValue() for example:ignore exception?flush_io(): ignore exceptionsys_write(),sys_format(): ignore exception_PyTraceback_Add(): ignore exceptionPyTraceBack_Print(): ignore exceptionExamples of function displaying the new exception tosys.stderr:
atexit_callfuncs(): display exceptions withPyErr_Display() and return the latest exception, the functioncalls multiple callbacks and only returns the latest exceptionsock_dealloc(): log theResourceWarning exception withPyErr_WriteUnraisable()slot_tp_del(): display exception withPyErr_WriteUnraisable()_PyGen_Finalize(): displaygen_close() exception withPyErr_WriteUnraisable()slot_tp_finalize(): display exception raised by the__del__() method withPyErr_WriteUnraisable()PyErr_GivenExceptionMatches(): display exception raised byPyType_IsSubtype() withPyErr_WriteUnraisable()A side effect of chaining exceptions is that exceptions storetraceback objects which store frame objects which store localvariables. Local variables are kept alive by exceptions. A commonissue is a reference cycle between local variables and exceptions: anexception is stored in a local variable and the frame indirectlystored in the exception. The cycle only impacts applications storingexceptions.
The reference cycle can now be fixed with the newtraceback.TracebackException object introduced in Python 3.5. Itstores information required to format a full textual traceback withoutstoring local variables.
Theasyncio is impacted by the reference cycle issue. This moduleis also maintained outside Python standard library to release aversion for Python 3.3.traceback.TracebackException will maybebe backported in a privateasyncio module to fix reference cycleissues.
A new private_PyErr_ChainExceptions() function is enough to chainmanually exceptions.
Exceptions will only be chained explicitly where it makes sense.
Functions likePyErr_SetString() don’t chain automaticallyexceptions. To make the usage of_PyErr_ChainExceptions() easier,new private functions are added:
_PyErr_SetStringChain(exc_type,message)_PyErr_FormatChain(exc_type,format,...)_PyErr_SetNoneChain(exc_type)_PyErr_SetObjectChain(exc_type,exc_value)Helper functions to raise specific exceptions like_PyErr_SetKeyError(key) orPyErr_SetImportError(message,name,path) don’t chain exceptions. The generic_PyErr_ChainExceptions(exc_type,exc_value,exc_tb) should be usedto chain exceptions with these helper functions.
The header fileInclude/pyerror.h declares functions related toexceptions.
Functions raising exceptions:
PyErr_SetNone(exc_type)PyErr_SetObject(exc_type,exc_value)PyErr_SetString(exc_type,message)PyErr_Format(exc,format,...)Helpers to raise specific exceptions:
PyErr_BadArgument()PyErr_BadInternalCall()PyErr_NoMemory()PyErr_SetFromErrno(exc)PyErr_SetFromWindowsErr(err)PyErr_SetImportError(message,name,path)_PyErr_SetKeyError(key)_PyErr_TrySetFromCause(prefix_format,...)Manage the current exception:
PyErr_Clear(): clear the current exception,likeexcept:passPyErr_Fetch(exc_type,exc_value,exc_tb)PyErr_Restore(exc_type,exc_value,exc_tb)PyErr_GetExcInfo(exc_type,exc_value,exc_tb)PyErr_SetExcInfo(exc_type,exc_value,exc_tb)Others function to handle exceptions:
PyErr_ExceptionMatches(exc): check to implementexceptexc: ...PyErr_GivenExceptionMatches(exc1,exc2)PyErr_NormalizeException(exc_type,exc_value,exc_tb)_PyErr_ChainExceptions(exc_type,exc_value,exc_tb)Chain exceptions:
_PyErr_ChainExceptions()Changes preventing to loose exceptions:
The PEP was rejected on 2017-09-12 by Victor Stinner. It was decided inthe python-dev discussion to not chain C exceptions by default, butinstead chain them explicitly only where it makes sense.
This document has been placed in the public domain.
Source:https://github.com/python/peps/blob/main/peps/pep-0490.rst
Last modified:2025-02-01 08:59:27 GMT