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.