1.3 Exceptions
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, until 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 in 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
objects sys.exc_type, sys.exc_value, and 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 bytecode interpreter's main loop, 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 discard any object references that it owns, and return an error
indicator, but it should not set another exception -- that would overwrite the
exception that was just raised, and lose important information 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
dict[key] = 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 (failure) */
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 successful.
|