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:
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 = ... 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.