Cython: exception type for a function returning a typed memoryview

橙三吉。 提交于 2019-12-14 02:07:57

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!