1.2 Intermezzo: Errors and Exceptions
An important convention throughout the Python interpreter is the following: when a function
fails, it should set an exception condition and return an error value (usually a NULL pointer). Exceptions are stored in a static global variable inside
the interpreter; if this variable is NULL no exception has occurred.
A second global variable stores the ``associated value'' of the exception (the second argument
to raise). A third variable contains the stack traceback in case the
error originated in Python code. These three variables are the C equivalents of the Python
variables sys.exc_type, sys.exc_value and sys.exc_traceback
(see the section on module sys in the Python Library Reference). It
is important to know about them to understand how errors are passed around.
The Python API defines a number of functions to set various types of exceptions.
The most common one is PyErr_SetString(). Its arguments are an
exception object and a C string. The exception object is usually a predefined object like PyExc_ZeroDivisionError. The C string indicates the cause of the error and
is converted to a Python string object and stored as the ``associated value'' of the
exception.
Another useful function is PyErr_SetFromErrno(), which only
takes an exception argument and constructs the associated value by inspection of the global
variable errno. The most general function is PyErr_SetObject(),
which takes two object arguments, the exception and its associated value. You don't need to Py_INCREF() the objects passed to any of these functions.
You can test non-destructively whether an exception has been set with PyErr_Occurred().
This returns the current exception object, or NULL if no exception
has occurred. You normally don't need to call PyErr_Occurred() to
see whether an error occurred in a function call, since you should be able to tell from the
return value.
When a function f that calls another function g detects that the
latter fails, f should itself return an error value (usually NULL
or -1). It should not call one of the PyErr_*()
functions -- one has already been called by g. f's caller is then
supposed to also return an error indication to its caller, again without calling
PyErr_*(), and so on -- the most detailed cause of the error was
already reported by the function that first detected it. Once the error reaches the Python
interpreter's main loop, this aborts the currently executing Python code and tries to find an
exception handler specified by the Python programmer.
(There are situations where a module can actually give a more detailed error message by
calling another PyErr_*() function, and in such cases it is fine to
do so. As a general rule, however, this is not necessary, and can cause information about the
cause of the error to be lost: most operations can fail for a variety of reasons.)
To ignore an exception set by a function call that failed, the exception condition must be
cleared explicitly by calling PyErr_Clear(). The only time C code
should call PyErr_Clear() is if it doesn't want to pass the error
on to the interpreter but wants to handle it completely by itself (possibly by trying
something else, or pretending nothing went wrong).
Every failing malloc() call must be turned into an exception --
the direct caller of malloc() (or realloc())
must call PyErr_NoMemory() and return a failure indicator itself.
All the object-creating functions (for example, PyInt_FromLong())
already do this, so this note is only relevant to those who call malloc()
directly.
Also note that, with the important exception of PyArg_ParseTuple()
and friends, functions that return an integer status usually return a positive value or zero
for success and -1 for failure, like Unix
system calls.
Finally, be careful to clean up garbage (by making Py_XDECREF()
or Py_DECREF() calls for objects you have already created) when you
return an error indicator!
The choice of which exception to raise is entirely yours. There are predeclared C objects
corresponding to all built-in Python exceptions, such as PyExc_ZeroDivisionError,
which you can use directly. Of course, you should choose exceptions wisely -- don't use PyExc_TypeError to mean that a file couldn't be opened (that should
probably be PyExc_IOError). If something's wrong with the argument
list, the PyArg_ParseTuple() function usually raises PyExc_TypeError. If you have an argument whose value must be in a
particular range or must satisfy other conditions, PyExc_ValueError is
appropriate.
You can also define a new exception that is unique to your module. For this, you usually
declare a static object variable at the beginning of your file:
static PyObject *SpamError;
and initialize it in your module's initialization function (initspam())
with an exception object (leaving out the error checking for now):
PyMODINIT_FUNC
initspam(void)
{
PyObject *m;
m = Py_InitModule("spam", SpamMethods);
SpamError = PyErr_NewException("spam.error", NULL, NULL);
Py_INCREF(SpamError);
PyModule_AddObject(m, "error", SpamError);
}
Note that the Python name for the exception object is spam.error.
The PyErr_NewException() function may create a class with the base
class being Exception (unless another class is passed in instead of
NULL), described in the Python Library Reference
under ``Built-in Exceptions.''
Note also that the SpamError variable retains a reference to the
newly created exception class; this is intentional! Since the exception could be removed from
the module by external code, an owned reference to the class is needed to ensure that it will
not be discarded, causing SpamError to become a dangling pointer.
Should it become a dangling pointer, C code which raises the exception could cause a core dump
or other unintended side effects.
We discuss the use of PyMODINIT_FUNC later in this sample.
|