LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 202|回复: 0

在C中调用Python函数

[复制链接]
发表于 2024-1-15 09:57:45 | 显示全部楼层 |阅读模式

在C中调用Python函数
迄今为止,我们一直把注意力集中于让Python调用C函数,其实反过来也很有用,就是用C调用Python函数。这在回调函数中尤其有用。如果一个C接口使用回调,那么就要实现这个回调机制。

幸运的是,Python解释器是比较方便回调的,并给标准Python函数提供了标准接口。(这里就不再详述解析Python字符串作为输入的方式,如果有兴趣可以参考 Python/pythonmain.c 中的 -c 命令行代码。)

调用Python函数很简单,首先Python程序要传递Python函数对象。应该提供个函数(或其他接口)来实现。当调用这个函数时,用全局变量保存Python函数对象的指针,还要调用 (Py_INCREF()) 来增加引用计数,当然不用全局变量也没什么关系。举个例子,如下函数可能是模块定义的一部分:

static PyObject *my_callback = NULL;

static PyObject *
my_set_callback(PyObject *dummy, PyObject *args)
{
    PyObject *result = NULL;
    PyObject *temp;

    if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {
        if (!PyCallable_Check(temp)) {
            PyErr_SetString(PyExc_TypeError, "parameter must be callable");
            return NULL;
        }
        Py_XINCREF(temp);         /* Add a reference to new callback */
        Py_XDECREF(my_callback);  /* Dispose of previous callback */
        my_callback = temp;       /* Remember new callback */
        /* Boilerplate to return "None" */
        Py_INCREF(Py_None);
        result = Py_None;
    }
    return result;
}
此函数必须使用 METH_VARARGS 旗标注册到解释器;这将在 模块方法表和初始化函数 一节中详细描述。 PyArg_ParseTuple() 函数及其参数的文档见 提取扩展函数的参数 一节。

Py_XINCREF() 和 Py_XDECREF() 这两个宏可增加/减少一个对象的引用计数,并且当存在 NULL 指针时仍可保证安全 (但请注意在这个上下文中 temp 将不为 NULL)。 更多相关信息请参考 引用计数 章节。

随后,当要调用此函数时,你将调用 C 函数 PyObject_CallObject()。 该函数有两个参数,它们都属于指针,指向任意 Python 对象:即 Python 函数,及其参数列表。 参数列表必须总是一个元组对象,其长度即参数的个数量。 要不带参数地调用 Python 函数,则传入 NULL 或一个空元组;要带一个参数调用它,则传入一个单元组。 Py_BuildValue() 会在其格式字符串包含一对圆括号内的零个或多个格式代码时返回一个元组。 例如:

int arg;
PyObject *arglist;
PyObject *result;
...
arg = 123;
...
/* Time to call the callback */
arglist = Py_BuildValue("(i)", arg);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
PyObject_CallObject() 返回Python对象指针,这也是Python函数的返回值。 PyObject_CallObject() 是一个对其参数 "引用计数无关" 的函数。例子中新的元组创建用于参数列表,并且在 PyObject_CallObject() 之后立即使用了 Py_DECREF() 。

PyObject_CallObject() 的返回值总是“新”的:要么是一个新建的对象;要么是已有对象,但增加了引用计数。所以除非你想把结果保存在全局变量中,你需要对这个值使用 Py_DECREF(),即使你对里面的内容(特别!)不感兴趣。

但是在你这么做之前,很重要的一点是检查返回值不是 NULL。 如果是的话,Python 函数会终止并引发异常。 如果调用 PyObject_CallObject() 的 C 代码是在 Python 中发起调用的,它应当立即返回一个错误来告知其 Python 调用者,以便解释器能打印栈回溯信息,或者让调用方 Python 代码能处理该异常。 如果这无法做到或不合本意,则应当通过调用 PyErr_Clear() 来清除异常。 例如:

if (result == NULL)
    return NULL; /* Pass error back */
...use result...
Py_DECREF(result);
依赖于具体的回调函数,你还要提供一个参数列表到 PyObject_CallObject() 。在某些情况下参数列表是由Python程序提供的,通过接口再传到回调函数对象。这样就可以不改变形式直接传递。另外一些时候你要构造一个新的元组来传递参数。最简单的方法就是 Py_BuildValue() 函数构造tuple。举个例子,你要传递一个事件代码时可以用如下代码:

PyObject *arglist;
...
arglist = Py_BuildValue("(l)", eventcode);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
if (result == NULL)
    return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);
注意 Py_DECREF(arglist) 所在处会立即调用,在错误检查之前。当然还要注意一些常规的错误,比如 Py_BuildValue() 可能会遭遇内存不足等等。

当你调用函数时还需要注意,用关键字参数调用 PyObject_Call() ,需要支持普通参数和关键字参数。有如如上例子中,我们使用 Py_BuildValue() 来构造字典。

PyObject *dict;
...
dict = Py_BuildValue("{s:i}", "name", val);
result = PyObject_Call(my_callback, NULL, dict);
Py_DECREF(dict);
if (result == NULL)
    return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表