【问题标题】:How do I inject a catchable exception in Python from C with C Python API?如何使用 C Python API 在 Python 中从 C 注入可捕获的异常?
【发布时间】:2019-03-11 15:11:32
【问题描述】:

我正在从 C 执行以下 Python 函数:

Python:

def runLoop(self):
    try:
        while True:
            print("working")
            time.sleep(1)  # Delay for 1 second.
            self.incrementcounter()  
    except:
        print("caught exception")

C:

//Here we register traceFunction to give us a hook into the python 
PyEval_SetProfile((Py_tracefunc)traceFunction, NULL);

//Abort interrupt reset to false
m_interruptRequest = false;

//Call the runLoop method
const char *szName = "runLoop";

PyObject_CallMethod(m_pyObject, szName, nullptr);

在这个循环的中间,我想从 C 中注入一个异常来中止循环,但也在 Python 中的 try/except 中处理异常。正如在 cmets 中提到的,我在 C 中注册了一个名为 tracer 的分析函数,能够从 C 中注入异常。下面的代码注入异常并杀死程序,因为它没有在 try/except 中捕获。

//Tracer 函数回调挂钩到 Python 执行。我们在这里处理暂停、恢复和中止请求。

void PythonRunner::tracer(void)
{
    ...
    //Abort requested
    else if (true == interruptRequested())
    {
        PyErr_SetString(PyExc_Exception, "ABORT");


    }
}

有谁知道我如何从 C 中抛出异常以在 Python 中处理?谢谢

更新:在@zwol 和这篇文章 stackoverflow.com/questions/1420957/stopping-embedded-python 的帮助下,我找到了解决方案。关键是向 python 添加一个挂起的调用,例如

    int PythonRunner::tracer(PyObject *, _frame *, int, PyObject *)
    {
        ...
        //Abort requested
        else if (true == _instance->interruptRequested())
        {
            Py_AddPendingCall(&PythonRunner::raiseException, NULL);
        }
    return 0;
   }

int PythonRunner::raiseException(void *)
{
    PyErr_SetString(PyExc_KeyboardInterrupt, "Abort");
    return -1;
}

【问题讨论】:

    标签: python c exception


    【解决方案1】:

    一般来说,要使用 CPython C API 引发异常,您需要调用 PyErr_Set* 函数之一然后从 API 函数返回一个特殊值。对于通常返回 Python 对象的函数,该特殊值是 NULL;对于其他功能,您必须检查文档,该文档可能仅存在于头文件中。

    在这种情况下,引用 pystate.h(来自 CPython 3.7):

    /* Py_tracefunc return -1 when raising an exception, or 0 for success. */
    typedef int (*Py_tracefunc)(PyObject *, struct _frame *, int, PyObject *);
    

    这意味着您必须使用匹配的签名声明PythonRunner::tracer,如下所示:

    class PythonRunner {
        // ...
    public:
        static int tracer(PyObject *, struct _frame *, int, PyObject *);
    }
    

    然后你可以返回 -1 表示异常,或 0 表示正常继续:

    int PythonRunner::tracer(PyObject *, struct _frame *, int, PyObject *)
    {
        ...
        // Abort requested?
        else if (interruptRequested())
        {
            PyErr_SetString(PyExc_Exception, "ABORT");
            return -1;
        }
        // Otherwise proceed normally.
        return 0;
    }
    

    此外,您对 PyEval_SetProfile 的调用将不再需要强制转换。

    PyEval_SetProfile(PythonRunner::tracer, NULL);
    

    我之所以提到这一点,是因为转换函数指针本身几乎总是一个错误,在这种情况下,认识到这可能会让您了解问题所在。

    (由于历史原因,Python“扩展和嵌入”文档的示例代码包含一堆不必要的强制转换和签名不正确的函数。每当您从该文档中复制代码时,删除其中的所有强制转换,然后小心检查编译器的抱怨,调整类型签名,并只放回不可避免的强制转换。)

    【讨论】:

    • 感谢您的快速回复。我根据您的 cmets 编辑了我的代码库,但不幸的是,我仍然遇到与以前类似的行为。我希望看到 Python 代码中捕获的异常,即要打印的“捕获的异常”。如果返回 -1 (PyErr_SetString(PyExc_Exception, "ABORT"); return -1;),则循环中止但未捕获异常。
    • 根据您提供给我们的信息,我对可能出现的问题没有任何其他想法。如果您能提供一个完整的示例程序,我可以用来为自己观察问题,我或许可以进一步帮助您。
    • 我在这里有一个 QT 示例:github.com/mbjapzon/pythonAbortException 希望你能从中收集到你需要的东西……这有点困难,因为你需要依赖 Python 和 QT。再次感谢您的宝贵时间。
    • 您必须修改 .pro 文件以指向您的 Python 版本。我使用的是 64 位 WinPython,但任何 64 位 Python 都应该足够了。
    • 不幸的是,我对 Qt 的了解不够,无法判断问题是否是 Qt 和 Python 之间的不良交互。你能尝试编写一个简化的测试程序来避免使用 Qt 吗?
    猜你喜欢
    • 2011-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-23
    • 2018-01-29
    • 2013-12-12
    • 2011-01-26
    相关资源
    最近更新 更多