The Python programmer only needs to deal with exceptions if specific error handling is required; unhandled exceptions are automatically propagated to the caller, then to the caller's caller, and so on, till they reach the top-level interpreter, where they are reported to the user accompanied by a stack traceback.
For C programmers, however, error checking always has to be explicit.
All functions in the Python/C API can raise exceptions, unless an
explicit claim is made otherwise in a function's documentation. In
general, when a function encounters an error, it sets an exception,
discards any object references that it owns, and returns an
error indicator - usually NULL or -1
. A few functions
return a Boolean true/false result, with false indicating an error.
Very few functions return no explicit error indicator or have an
ambiguous return value, and require explicit testing for errors with
PyErr_Occurred()
.
Exception state is maintained in per-thread storage (this is
equivalent to using global storage in an unthreaded application). A
thread can be on one of two states: an exception has occurred, or not.
The function PyErr_Occurred()
can be used to check for this: it
returns a borrowed reference to the exception type object when an
exception has occurred, and NULL otherwise. There are a number
of functions to set the exception state: PyErr_SetString()
is
the most common (though not the most general) function to set the
exception state, and PyErr_Clear()
clears the exception state.
The full exception state consists of three objects (all of which can
be NULL ): the exception type, the corresponding exception
value, and the traceback. These have the same meanings as the Python
object sys.exc_type
, sys.exc_value
,
sys.exc_traceback
; however, they are not the same: the Python
objects represent the last exception being handled by a Python
try...except
statement, while the C level exception state only
exists while an exception is being passed on between C functions until
it reaches the Python interpreter, which takes care of transferring it
to sys.exc_type
and friends.
(Note that starting with Python 1.5, the preferred, thread-safe way to
access the exception state from Python code is to call the function
sys.exc_info()
, which returns the per-thread exception state
for Python code. Also, the semantics of both ways to access the
exception state have changed so that a function which catches an
exception will save and restore its thread's exception state so as to
preserve the exception state of its caller. This prevents common bugs
in exception handling code caused by an innocent-looking function
overwriting the exception being handled; it also reduces the often
unwanted lifetime extension for objects that are referenced by the
stack frames in the traceback.)
As a general principle, a function that calls another function to perform some task should check whether the called function raised an exception, and if so, pass the exception state on to its caller. It should discards any object references that it owns, and returns an error indicator, but it should not set another exception - that would overwrite the exception that was just raised, and lose important reason about the exact cause of the error.
A simple example of detecting exceptions and passing them on is shown
in the sum_sequence()
example above. It so happens that that
example doesn't need to clean up any owned references when it detects
an error. The following example function shows some error cleanup.
First, to remind you why you like Python, we show the equivalent
Python code:
def incr_item(dict, key): try: item = dict[key] except KeyError: item = 0 return item + 1
Here is the corresponding C code, in all its glory:
int incr_item(PyObject *dict, PyObject *key) { /* Objects all initialized to NULL for Py_XDECREF */ PyObject *item = NULL, *const_one = NULL, *incremented_item = NULL; int rv = -1; /* Return value initialized to -1 (faulure) */ item = PyObject_GetItem(dict, key); if (item == NULL) { /* Handle keyError only: */ if (!PyErr_ExceptionMatches(PyExc_keyError)) goto error; /* Clear the error and use zero: */ PyErr_Clear(); item = PyInt_FromLong(0L); if (item == NULL) goto error; } const_one = PyInt_FromLong(1L); if (const_one == NULL) goto error; incremented_item = PyNumber_Add(item, const_one); if (incremented_item == NULL) goto error; if (PyObject_SetItem(dict, key, incremented_item) < 0) goto error; rv = 0; /* Success */ /* Continue with cleanup code */ error: /* Cleanup code, shared by success and failure path */ /* Use Py_XDECREF() to ignore NULL references */ Py_XDECREF(item); Py_XDECREF(const_one); Py_XDECREF(incremented_item); return rv; /* -1 for error, 0 for success */ }
This example represents an endorsed use of the goto
statement
in C! It illustrates the use of PyErr_ExceptionMatches()
and
PyErr_Clear()
to handle specific exceptions, and the use of
Py_XDECREF()
to dispose of owned references that may be
NULL (note the `X' in the name; Py_DECREF()
would crash
when confronted with a NULL reference). It is important that
the variables used to hold owned references are initialized to
NULL for this to work; likewise, the proposed return value is
initialized to -1
(failure) and only set to success after
the final call made is succesful.
guido@CNRI.Reston.Va.US