Cython函数指针和异常

时间:2018-12-29 20:18:03

标签: python cython function-pointers

我正在尝试使用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 ...语句是否错误?

1 个答案:

答案 0 :(得分:2)

这就是Cython试图防止您犯的细微错误。

首先,让我们回想一下CPython中错误处理的工作方式:存在全局错误状态(每个线程),该状态在发生错误/异常时设置。此状态包含有关异常类型,回溯等的信息。约定是,除了设置全局错误状态外,函数还会通过特殊的返回值来指示其故障,因此不必在每次调用函数后都检查错误状态。

一旦在功能中检测到故障,则必须发生以下情况:

  • 如果此函数“知道”如何处理此错误(例如“ except”子句),那么它必须先清除全局错误状态,然后再继续。
  • 如果此函数不“知道”如何处理错误,则必须中止并返回失败信号。

重要的一点:如果函数未报告已发生的错误,则应清除错误状态,否则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)