`PyErr_SetObject()` behavior is strange and not as documented.

markshannon · GitHub

Briefly:
PyErr_SetObject(exc_type, exc_val) does not create a new exception iff isinstance(exc_val, BaseException), but uses exc_val instead.

Callers of PyErr_SetObject() need various workarounds to handle this.

The long version:

Internally CPython handles exceptions as a triple (type, value, traceback), but the language treats exceptions as a single value.

This a legacy of the olden days before proper exceptions.
To handle adding proper exceptions to Python, various error handling functions, specifically _PyErr_SetObject still treat exceptions as triples, with the convention that if the value is an exception, then the exception is already normalized.

One other oddity is that if exc_val is a tuple, it is treated as the * arguments to exc_type when calling it. So, if isinstance(exc_val, BaseException) the desired behavior can be achieved by wrapping exc_val in a one-tuple.

As a consequence, both _PyErr_SetKeyError and _PyGen_SetStopIterationValue are a lot more complex than they should be to workaround this behavior.

We could make PyErr_SetObject act as documented, but that is likely to break C extensions, given how old this behavior is, and that it is relied on throughout CPython.

Code that does the following is common:

exc=new_foo_exception(); PyErr_SetObject(&PyFooException_Type, exc);We could just document the current behavior, but the current behavior is strange.
What I suggest is this:

Create a new API function, PyErr_SetException(exc)` that takes a single exception object.

Document PyErr_SetObject() accurately

Deprecate the old function

This is an old bug going back to the 2 series.

Linked PRs

GH-101578: Normalize the current exception #101607

gh-101578: Amend PyErr_{Set,Get}RaisedException docs #101962

gh-101578: Amend exception docs #102057

gh-101578: Fixup NEWS and add What's New entry for new exception APIs #102157

gh-101578: mention in what's new in 3.12 that exceptions are now normalized before stored #102702

gh-101578: [doc] mention that PyErr_GetRaisedException returns NULL when the error indicator is not set #113369

[3.12] gh-101578: [doc] mention that PyErr_GetRaisedException returns NULL when the error indicator is not set (GH-113369) #113606

Also relevant:

gh-102493: fix normalization in PyErr_SetObject #102502