Skip to content

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., NULL for pointers or -1 for 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 NULL if 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 0 on success, -1 (with an exception set) on failure.
  • Snippet:
    if (PyErr_CheckSignals()) {
        return NULL; // Propagate KeyboardInterrupt
    }
    

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

  1. Check Return Values: Always check if a function returns NULL or -1.
  2. Propagate Promptly: If a function you call fails, generally return the error value immediately to let the caller handle it.
  3. Reference Counting: Be careful with Py_DECREF during error handling. Ensure you don't leak objects when an error occurs halfway through a function.
  4. Clean Up: Use PyErr_Clear() if you catch an exception and handle it internally so it doesn't leak into unrelated code.