我正在尝试使用cython包装现有的C库。该库使用我想重定向的回调来执行python代码。 可以说标题中的相应行如下:
typedef RETCODE (*FUNC_EVAL)(int a, int b, void* func_data);
其中,返回码用于表示错误。的API 创建相应的C结构如下:
RETCODE func_create(Func** fstar,
FUNC_EVAL func_eval,
void* func_data);
我添加了一个cython头文件/实现文件。标头 包含typedef:
ctypedef RETCODE (*FUNC_EVAL)(int a,
int b,
void* func_data)
该实现包含包装函数:
cdef RETCODE func_eval(int a,
int b,
void* func_data):
(<object> func_data).func_eval(a, b)
return OKAY;
我可以将该函数传递给func_create
cython包装器。
但是,我要确保python代码中的异常是
通过返回一个ERROR
值返回给C库
返回码。所以我添加了以下内容:
cdef RETCODE func_eval(int a,
int b,
void* func_data) except ERROR:
(<object> func_data).func_eval(a, b)
return OKAY;
但是,现在cython终止并显示以下错误消息:
Cannot assign type 'RETCODE (*)(int, int, void *) except ERROR' to 'FUNC_EVAL'
我使用except ...
语句是否错误?
答案 0 :(得分:2)
这就是Cython试图防止您犯的细微错误。
首先,让我们回想一下CPython中错误处理的工作方式:存在全局错误状态(每个线程),该状态在发生错误/异常时设置。此状态包含有关异常类型,回溯等的信息。约定是,除了设置全局错误状态外,函数还会通过特殊的返回值来指示其故障,因此不必在每次调用函数后都检查错误状态。
一旦在功能中检测到故障,则必须发生以下情况:
重要的一点:如果函数未报告已发生的错误,则应清除错误状态,否则python解释器将处于异常状态,并可能发生细微错误:例如Cython cdef
-functions with except?
取决于正确的错误状态(例如,不同的Cython的except
子句如何工作,例如this SO-answer)。
现在,回到您的cdef
函数。
except
,则Cython会处理全局状态:如果发生错误,则在函数返回默认值之前清除状态(并将警告写入标准错误)。 except 1
声明的,则函数的调用者必须注意清除错误状态。问题是:FUNC_EVAL
-functor的调用者是否在发生错误的情况下清除Python的错误状态?
ctypedef... (*FUNC_EVAL)(...) except 1
,以使Cython清楚地知道调用者将能够处理该错误。cdef
函数中处理Python的错误状态。在“否”的情况下,最直接的方法是在try: ... except: ...
函数中使用cdef
,即
cdef RETCODE func_eval(int a,
int b,
void* func_data):
try:
(<object> func_data).func_eval(a, b)
except Exception:
return ERROR
return OKAY
可能有人担心,即使没有引发异常的情况,使用try... except...
也会增加开销。这是真的。但是,您已经在调用某些Python功能,因此这种额外的开销不会降低性能。
我的快速实验表明,如果所调用的Python功能根本没有计算,您可能会损失多达30%(请参见答案附录中的实验)。但是以上是一个极端的情况,通常情况下您的损失会少得多,因此除非分析器显示这确实是问题,否则我不会尝试对其进行优化。
如果您定义了ERROR=0
和'OKAY = 1 , so you can use the implementation detail, that Cython sets the result to
0`,则清除错误。但是,这似乎是一条湿滑的道路。
开销的测量:
%%cython -a
cdef extern from *:
"""
typedef int (*FUN)(void);
void call(FUN f){
f();
}
"""
ctypedef int (*FUN)()
void call(FUN f)
def dummy():
pass
cdef int cython_handling():
dummy()
return 1
cdef int manual_handling():
try:
dummy()
except Exception:
return 0
return 1
def check_cython():
cdef int i
for i in range(1000):
call(cython_handling)
def check_manually():
cdef int i
for i in range(1000):
call(manual_handling)
现在:
%timeit check_cython()
# 21.6 µs ± 164 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit check_manually()
# 27 µs ± 493 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)