PyErr_Print vs PyErr_Display vs PyErr_WriteUnraisable

There are three ways to handle an exception if it cannot be passed to the caller in the C code:

  • PyErr_WriteUnraisable()/PyErr_FormatUnraisable(). It was initially the most basic way. It writes “Exception ignored …” on the stderr and then writes the current exception name and message. No tracebacks, no notes, no exception chains. This is what you usually get if the exception is raised in the destructor or callback. Now it can be customizable by the sys.unraisablehook hook.
  • PyErr_Display()/PyErr_DisplayException(). It writes the specified exception in the way we used to to the stderr – with tracebacks, notes and recursively with __context__ and __cause__ chains. PyErr_Display() can have a side effect of setting __traceback__ of the excedption. Now it is implemented in Python – imports and calls a function from the traceback module.
  • PyErr_Print()/PyErr_PrintEx(). In interactive mode it prints the exit message and exits the program by calling Py_Exit() if the current exception is SystemExit. Otherwise it can set sys.last_exc and other variable and calls the sys.excepthook hook which uses PyErr_Display() by default.

Unfortunately, using these functions is not consistent across the extension code. I believe that PyErr_Print() is completely unappropriate in the library code due to its side effects – possibility to exit the program and asynchronous changing the sys attributes. It only should be used in the REPL and at the highest level of some programs. There may be some use for PyErr_Display(), but it is not so handy and flexible. You need to explicitly write the context of the error to the stderr before writing the exception, and there is no way to catch the output except substitude the stderr.

PyErr_FormatUnraisable() looks the most appropriate tool. It lacks traceback and other details, but importing from the traceback module may be unappropriate – the import machinery may not work at that stage (too early or too late).