Python C API: Exception Handling¶
This guide covers the mechanisms provided by the Python C API for managing errors and exceptions. In C, Python's exception handling relies on a global (thread-local) error indicator rather than a native try/catch mechanism.
The Error Indicator¶
The Python interpreter maintains an "error indicator" for every thread. It consists of three parts:
- The Exception Type: A pointer to a Python exception class (usually a subclass of
BaseException). - The Exception Value: A pointer to an instance of that class (containing the error message or arguments).
- The Traceback: A pointer to a traceback object tracking the call stack.
Key Rules¶
- If the indicator is NULL, no exception is pending.
- Most API functions return a specific value (e.g.,
NULLfor pointers or-1for integers) to signal that an error occurred. - Important: Returning an error value does not automatically clear the indicator; you must explicitly set or clear it using the API.
Signaling Exceptions¶
To "raise" an exception from C, you use functions that set the thread's error indicator.
Basic Functions¶
void PyErr_SetString(PyObject *type, const char *message)- The most common way to raise an error.
-
Example:
PyErr_SetString(PyExc_TypeError, "Expected an integer"); -
void PyErr_SetObject(PyObject *type, PyObject *value) -
Similar to
SetString, but uses a Python object as the value. -
PyObject* PyErr_Format(PyObject *exception, const char *format, ...) - Allows C-style printf formatting for the error message.
Checking for Exceptions¶
Since C does not have automated exception propagation, you must check the status of the indicator manually after calling Python API functions.
Functions¶
PyObject* PyErr_Occurred()-
Returns a borrowed reference to the exception type if an error is pending, or
NULLif not. -
int PyErr_ExceptionMatches(PyObject *exc) -
Checks if the pending exception is of a specific type.
-
void PyErr_Clear() - Clears the error indicator. Necessary if you intend to ignore an error or handle it locally without propagating it to the caller.
Standard Exception Objects¶
Python provides global variables for all standard built-in exceptions. These are pointers to PyObject.
| C Variable | Python Equivalent |
|---|---|
PyExc_Exception |
Exception |
PyExc_TypeError |
TypeError |
PyExc_ValueError |
ValueError |
PyExc_IndexError |
IndexError |
PyExc_RuntimeError |
RuntimeError |
PyExc_SystemError |
SystemError |
Signal Handling and Interruption¶
C extensions that perform long-running computations should periodically check if the user has attempted to interrupt the process (e.g., via Ctrl+C).
int PyErr_CheckSignals()- Checks if a signal (like
SIGINT) has been received. - Returns
0on success,-1(with an exception set) on failure. - Snippet:
The "Fetch and Restore" Pattern¶
If you need to execute Python code while an exception is already pending (for example, inside a cleanup routine), you must save the current state to avoid overwriting it.
void PyErr_Fetch(PyObject **ptype, PyObject **pvalue, PyObject **ptraceback)-
Retrieves and clears the error indicator.
-
void PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) - Sets the error indicator back to the saved state.
Warnings¶
Warnings are handled differently than exceptions. They do not necessarily stop execution but can be configured to act like errors.
int PyErr_WarnEx(PyObject *category, const char *message, Py_ssize_t stack_level)- Issues a warning.
- If the warning is converted to an error by the user's settings, it returns
-1.
Best Practices¶
- Check Return Values: Always check if a function returns
NULLor-1. - Propagate Promptly: If a function you call fails, generally return the error value immediately to let the caller handle it.
- Reference Counting: Be careful with
Py_DECREFduring error handling. Ensure you don't leak objects when an error occurs halfway through a function. - Clean Up: Use
PyErr_Clear()if you catch an exception and handle it internally so it doesn't leak into unrelated code.