【问题标题】:Cython: Converting a Cython function with `except` into C like functionCython:将带有“except”的 Cython 函数转换为类似 C 的函数
【发布时间】:2021-07-13 02:33:01
【问题描述】:

我基本上是在尝试包装一个 C 库,并希望使用 Cython 在包装的 C 函数中传递 Python 函数。我正在使用全局变量来存储 Python 函数,在 Cython 包装器中调用它,然后将 Cython 包装器作为 C 函数传递给其他 C 函数。我想在被 Cython 包装器调用时捕获 Python 函数中的错误,因此,我在 Cython 包装器声明的末尾添加了 except? -1。

问题是当我使用 except 关键字传递 Cython 函数时,它无法将其转换为类似 C 的函数。如果我不这样做,我将无法处理 Python 函数中发生的错误(我需要这样做,因为该函数是在循环中调用的)

MRE 如下所示。

# file: mre.pyx

# cython: language_level=3

import numpy as np
cimport numpy as np

_glob_params = None
_glob_func = None

cdef extern from "mre.h":
    double eval_func(double (*func)(double, double *, int))

cdef double _func_wrapper(double x, double *params, int n) except? -1:
    return _glob_func(x, *_glob_params)

def run_func(func, params=(), Py_ssize_t size=100):
    cdef Py_ssize_t i
    cdef np.ndarray[np.float64_t, ndim=1] out = np.empty(size, dtype=np.float64)
    global _glob_func, _glob_params

    _glob_func = func
    _glob_params = params

    for i in range(size):
        out[i] = eval_func(_func_wrapper)

    return out
// file : mre.h

#pragma once

#include <stdlib.h>
#include <stdio.h>

double eval_func(double (*func)(double, double *, int)) {
    double res = func(1.0, NULL, 0);
    if ( res < 0. ) {
        fprintf(stderr, "ERROR: func < 0. -> not allowed!\n");
    }
    return res;
}

错误:

Error compiling Cython file:
------------------------------------------------------------
...

    _glob_func = func
    _glob_params = params

    for i in range(size):
        out[i] = eval_func(_func_wrapper)
                          ^
------------------------------------------------------------

mre.pyx:24:27: Cannot assign type 'double (double, double *, int) except? -1.0' to 'double (*)(double, double *, int)'

没有except? -1:

>>> from mre import run_func
>>> run_func(None)
TypeError: 'NoneType' object is not callable
Exception ignored in: 'mre._func_wrapper'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable
TypeError: 'NoneType' object is not callable
Exception ignored in: 'mre._func_wrapper'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
             ...
             ...
             ...
             ...
TypeError: 'NoneType' object is not callable
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

那么,有没有办法在没有except 的情况下处理 Cython 中的异常?我尝试使用ctypes 包装Python 函数,但在实践中非常慢。

【问题讨论】:

  • 我觉得你需要把函数地址拿进去:out[i] = eval_func(&amp;_func_wrapper)
  • 不,不起作用。我认为函数已经是 C 中的指针。因此,您不需要传递显式引用。
  • 所以您想将错误传播到 eval_func?也许,在 cdef extern from "mre.h" 中尝试将 except? -1 添加到 eval_func 函数的声明中。
  • 好的。经过试验,我只能抛出一次错误。我通过修改 mre.h 中的 eval_func 以使用 throw std::runtime_error 来抛出此错误。然后,在 mre.pyx 中用“except +”声明 eval_func。我将 mre.pyx 编译为 c++ 而不是 c。 c++ 抛出的新错误在 python 中被捕获,消息为“RuntimeError: ERROR: func not allowed!”。这种情况发生一次。这是你想要的吗?我可以写一个完整的答案。
  • 澄清一下,我让它以一种可以在 Python 中处理的方式抛出一次,而不是相同的错误发生 100 次但从未被捕获。

标签: python c cython


【解决方案1】:

异常在 Python 中的工作方式,一旦处理了异常,就必须更改全局停留,即必须清除错误(例如通过PyErr_Clear()),否则解释器处于不一致状态,可能会发生奇怪的事情。

由于您的 C 代码不了解 Python,因此无法清除错误指示符,因此它必须发生在 Cython 代码中。一种可能性是添加一个额外的包装器:

cdef double wrapper_noraise(double x, double *params, int n):
    try:
        return _func_wrapper(val)
    except:
        return -1.0

现在将 wrapper_noraise 传递给 C 代码。

或者,在您的情况下,您可以直接将_func_wrapper 更改为:

cdef double _func_wrapper(double x, double *params, int n):
    try:
       return _glob_func(x, *_glob_params)
    except:
       return -1.0

【讨论】:

  • 所以不是 cython 忽略异常,而是手动处理它?是否可以将异常传播到 Python?
  • @GoldenRockefeller 在 OP 的版本中,Cython 不会忽略异常 - 它会中止函数的执行但不会清除错误指示符,因此会通知函数的调用者,即发生错误并做出相应反应。然而,由于抛出函数的调用者是对 Python 异常一无所知的 C 代码,因此确保 python-interpreter 不会处于不一致状态的唯一方法是清除错误指示符并将其转换为错误被 C 代码理解(即结果
  • @GoldenRockefeller 如果你想有一种可持续的方式将错误传播回 Python,应该有办法知道 C 代码中存在错误(现在这是错误消息到 stderr - 不是很方便,但可以使用),然后在执行 C 代码后检测此状态并引发 Python 异常。
  • 为了澄清,我的意思是“忽略异常”,指的是 OP 的回答中的输出“异常忽略:'mre._func_wrapper'”,而不是停止程序。我认为重复此消息的原因是因为 Python 的错误指示符正在被清除。几乎就像,在打印此消息后,代码假装错误没有发生。
  • @GoldenRockefeller 好的,我现在不明白。是的,Cython 会进行检查,但在两者之间,解释器处于不一致的状态。一个人可能很幸运,中间什么都没有发生,但一个人也可能不走运,C 代码可能会调用另一个 Python 代码,因此会做一些奇怪的事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-23
  • 2015-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多