问题
In a cdef
signature of a function:
cdef const unsigned char[:, :] my_fn(input) except <????> :
What shall I put in <????>
?
If I understand the documentation correctly, specifying an exception type is necessary for exception to propagate up the Python stack.
I tried things as [b'\x00']
and empty Cython arrays, none works.
回答1:
Bad news: You can't do it. Good news: You don't have to do it!
The syntax with except <xxx>
is only possible if the cdef
function returns an int
, an enum, a float or a pointer - basically something for which it makes sense to compare via ==
in C.
A typed memory view is a Python-object, which has a built-in way to signal an error - when the returned object is a null-pointer. Thus, you don't have to define an exceptional value because it is already defined!
For example:
%%cython
cdef int[:] worker(int[:] memview, int index):
memview[index]=10
return memview
def runit(index):
cdef int mem[4]
print(worker(mem,index))
And now
runit(4) #4 -> out of bounds
print("I still run")
doesn't print "I still run" because the out-of-bounds exception is propagated.
This would not be the case for a return value, which is not a Python object, for example an int
:
%%cython
cdef int worker(int[:] memview, int index):
return memview[index]
And now:
runit(4) #4 -> out of bounds
print("I still run")
prints "0" and "I still run", because the error is not propagated. We can choose an exceptional value, for example -1
so the error is propagated via return-value=-1:
%%cython
cdef int worker(int[:] memview, int index) except -1:
return memview[index]
Now, "I still run" is no longer printed.
However, sometimes there is no good exceptional value, for example because memview
could contain any integer value:
%%cython
cdef int worker(int[:] memview, int index) except -1:
return memview[index]
def runit(index):
cdef int mem[4]
mem[0]=-1
print(worker(mem, index))
Now, running
runit(0)
print("I still run")
ends in a spurious error:
SystemError: returned NULL without setting an error
The solution is to use
cdef int worker(int[:] memview, int index) except *
which has the right behavior for runit(0)
and runit(4)
.
So what are the costs of using except *
compared to except -1
? They are not really high:
If the returned value is -1
(this is the default "exceptional" value), then we know that an error could have happened (it is only a possibility not a certainty) and check via PyErr_Occurred()
, whether it is really the case.
As @DavidW mentioned in the comment, it is also possible to use except? -1
which has the advantage that it is easier to read and to understand. The funny thing is, that this would produce the same C-code as except *
, because the default error-value is -1
!
However, the except?
-syntax allows us to choose the function result for which we have to pay the overhead of PyErr_Occurred()
. If for example, we would know, that the result -1
occurs pretty often and -2
almost never, so we could use except? -2
and PyErr_Occured()
would be checked only if the result of the function is -2
, that means almost never (in the case of except *
it would be checked pretty often - every time -1
is returned).
来源:https://stackoverflow.com/questions/50684977/cython-exception-type-for-a-function-returning-a-typed-memoryview