Managing exceptions

Any cdef function in Cython which has a typed return cannot report Python extensions to its caller. This is because, when the return value is typed, it no longer returns a Python object (which is the default behaviour of all Cython, and Python, functions). As a result, in a function such as the one below:

cdef double inv_double(double x):
    if x == 0.0:
        raise ValueError("Division by zero.")

    return 1.0/x

the exception, in this case a ValueError instance, will be created, reported and destroyed all within the function scope. The function will then return the default return type value from the raised exception point - in this case, it would return a value of 0.0 if x == 0.0.

There are two solutions to this problem, but in the Finesse Cython code we should only use one of these solutions - this is detailed below. The other solution is to not declare a return type for any cdef function which can raise an exception, such that the return type is then PyObject*. This is not ideal, however, as it doesn’t allow Cython to fully-optimise the C source file it produces - more specifically, it results in calls to the CPython API functions: __Pyx_XDECREF, __Pyx_XGIVEREF and __Pyx_AddTraceback which are required when dealing with Python objects.

The solution which we use instead is documented in Cython error return values. Here, one explicitly declares that the function can raise and provides a value (of the same type as the return) which indicates that the function has raised. Following this, the general pattern we use is then shown in this pseudo-code snippet:

# Ty is the type of the parameter we want to compute with the function
cdef int func(Ty* result, other_args...) except -1:
    if variable in other_args is invalid:
        raise an exception

    # ...
    # do some calculations using other_args
    # ...

    # note this is equivalent to *result = ... in C, Cython uses array
    # access for pointers as *result is not valid Python syntax
    result[0] = ...

    return 0

This pattern informs Cython that an exception has been thrown when func returns -1. Cython then goes off and deals with reporting this to the caller and handles the traceback.