GNU Info

Info Node: (python2.1-ext.info)Intermezzo Errors and Exceptions

(python2.1-ext.info)Intermezzo Errors and Exceptions


Next: Back to the Example Prev: A Simple Example Up: Extending Python with C or C++
Enter node , (file) or (file)node

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 ).  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 (e.g. `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 (e.g. by trying something else or pretending nothing
happened).

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,
e.g. `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, e.g.

     static PyObject *SpamError;

and initialize it in your module's initialization function
(`initspam()') with an exception object, e.g. (leaving out the error
checking for now):

     void
     initspam()
     {
         PyObject *m, *d;
     
         m = Py_InitModule("spam", SpamMethods);
         d = PyModule_GetDict(m);
         SpamError = PyErr_NewException("spam.error", NULL, NULL);
         PyDict_SetItemString(d, "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  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.


automatically generated by info2www version 1.2.2.9