Next: Back to the Up: Extending Python with Previous: A first look

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 (often a NULL pointer). Exceptions are stored in a static global variable in `Python/errors.c'; if this variable is NULL no exception has occurred. A second static global variable stores the `associated value' of the exception - the second argument to raise.

The file `errors.h' declares a host of functions to set various types of exceptions. The most common one is err_setstr() - its arguments are an exception object (e.g. RuntimeError - actually it can be any string object) and a C string indicating the cause of the error (this is converted to a string object and stored as the `associated value' of the exception). Another useful function is err_errno(), which only takes an exception argument and constructs the associated value by inspection of the (UNIX) global variable errno. The most general function is err_set(), which takes two object arguments, the exception and its associated value. You don't need to INCREF() the objects passed to any of these functions.

You can test non-destructively whether an exception has been set with err_occurred(). However, most code never calls err_occurred() to see whether an error occurred or not, but relies on error return values from the functions it calls instead.

When a function that calls another function detects that the called function fails, it should return an error value (e.g. NULL or -1) but not call one of the err_* functions - one has already been called. The caller is then supposed to also return an error indication to its caller, again without calling err_*(), and so on - the most detailed cause of the error was already reported by the function that first detected it. Once the error has reached Python's interpreter 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 err_* 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 err_clear(). The only time C code should call err_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).

Finally, the function err_get() gives you both error variables and clears them. Note that even if an error occurred the second one may be NULL. You have to XDECREF() both when you are finished with them. I doubt you will need to use this function.

Note that a failing malloc() call must also be turned into an exception - the direct caller of malloc() (or realloc()) must call err_nomem() and return a failure indicator itself. All the object-creating functions (newintobject() etc.) already do this, so only if you call malloc() directly this note is of importance.

Also note that, with the important exception of getargs(), functions that return an integer status usually return 0 or a positive value for success and -1 for failure.

Finally, be careful about cleaning up garbage (making XDECREF() or DECREF() calls for objects you have already created) when you return an error!

The choice of which exception to raise is entirely yours. There are predeclared C objects corresponding to all built-in Python exceptions, e.g. ZeroDevisionError which you can use directly. Of course, you should chose exceptions wisely - don't use TypeError to mean that a file couldn't be opened (that should probably be IOError). If anything's wrong with the argument list the getargs() function raises TypeError. If you have an argument whose value which must be in a particular range or must satisfy other conditions, 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 object *FooError;

and initialize it in your module's initialization function (initfoo()) with a string object, e.g. (leaving out the error checking for simplicity):


    void
    initfoo()
    {
        object *m, *d;
        m = initmodule("foo", foo_methods);
        d = getmoduledict(m);
        FooError = newstringobject("foo.error");
        dictinsert(d, "error", FooError);
    }



Next: Back to the Up: Extending Python with Previous: A first look


guido@cwi.nl