返回多个值的 cdef 函数的 Cython 异常传播

问题描述

我有一个 cdef 函数返回一个 (int,int) 元组。我需要传播异常,因此必须为异常指定返回类型。由于我的函数从不返回负值,因此这可能例如(-1,-1)。使用 documentation 中指定的标准语法,我的函数看起来像

cdef (int,int) myFunction(int arg) except (-1,-1):
    ...

但是,在对上述函数进行 cythonizing 时,出现错误

Not allowed in a constant expression

我知道我可以在每次函数调用后通过

打开查找异常
cdef (int,int) myFunction(int arg) except *:
    ...

但这对我来说似乎效率低下。

如何为具有多个返回值的函数传播异常?

解决方法

答案是:您不能,至少不能使用 Cython 的 cdef 元组的当前版本。

潜在的问题是,对于此类元组,Cython 未定义运算符 ==(这显然可以通过比较每个条目来完成 - 但在当前版本中未定义)。

对于一个简单的 cdef int myfun() except * 实际上并不是一个很大的性能损失:

  • 如果结果不是 -1,则几乎没有任何开销(仅与 -1 进行比较)。
  • 如果结果是 -1,则使用 PyErr_Occurred 来检查错误,这可能意味着相当大的开销(以上都是 nogil-blocks)
  • 可以通过 X 选择一个临界值 except? X,从而最小化/优化必要的 PyErr_Occured 调用次数。

然而,如果 Cython 不知道类型的 ==-operator(如 Cython 的 c-tuples),我们基本上处于 cdef void myfun() except * 情况,这意味着没有捷径和 {{ 1}} 必须始终检查/调用。


您可能会说 PyErr_Occured 是为 Cython 的元组定义的,因为它是经过编译的。但是如果您查看生成的代码,您会发现,为了进行比较,Cython 的 ctuples 被转换为 Python-tuples。


我个人首先会选择==,看看它是否真的产生了可衡量的影响。因为 except *-function 显然可以与 Python 交互,所以多加一点可能根本没有伤害。

如果是这样,最好的可能是更改函数的签名,例如

cdef

即使这感觉不那么流畅 - 由于更好的性能,它可能值得麻烦。