你想在 C 中安全的执行某个 Python 调用并返回结果给 C。 例如,你想在 C 语言中使用某个 Python 函数作为一个回调。
解决方案
在 C 语言中调用 Python 非常简单,不过涉及到一些小窍门。 下面的 C 代码告诉你怎样安全的调用:
#include <Python.h>
/* Execute func(x,y) in the Python interpreter. The
arguments and return result of the function must
be Python floats */
double call_func(PyObject *func, double x, double y) {
PyObject *args;
PyObject *kwargs;
PyObject *result = 0;
double retval;
/* Make sure we own the GIL */
PyGILState_STATE state = PyGILState_Ensure();
/* Verify that func is a proper callable */
if (!PyCallable_Check(func)) {
fprintf(stderr,"call_func: expected a callable\n");
goto fail;
}
/* Build arguments */
args = Py_BuildValue("(dd)", x, y);
kwargs = NULL;
/* Call the function */
result = PyObject_Call(func, args, kwargs);
Py_DECREF(args);
Py_XDECREF(kwargs);
/* Check for Python exceptions (if any) */
if (PyErr_Occurred()) {
PyErr_Print();
goto fail;
}
/* Verify the result is a float object */
if (!PyFloat_Check(result)) {
fprintf(stderr,"call_func: callable didn't return a float\n");
goto fail;
}
/* Create the return value */
retval = PyFloat_AsDouble(result);
Py_DECREF(result);
/* Restore previous GIL state and return */
PyGILState_Release(state);
return retval;
fail:
Py_XDECREF(result);
PyGILState_Release(state);
abort(); // Change to something more appropriate
}
要使用这个函数,你需要获取传递过来的某个已存在 Python 调用的引用。 有很多种方法可以让你这样做, 比如将一个可调用对象传给一个扩展模块或直接写 C 代码从已存在模块中提取出来。
下面是一个简单例子用来演示从一个嵌入的 Python 解释器中调用一个函数:
#include <Python.h>
/* Definition of call_func() same as above */
//...
/* Load a symbol from a module */
PyObject *import_name(const char *modname, const char *symbol) {
PyObject *u_name, *module;
u_name = PyUnicode_FromString(modname);
module = PyImport_Import(u_name);
Py_DECREF(u_name);
return PyObject_GetAttrString(module, symbol);
}
/* Simple embedding example */
int main() {
PyObject *pow_func;
double x;
Py_Initialize();
/* Get a reference to the math.pow function */
pow_func = import_name("math","pow");
/* Call it using our call_func() code */
for (x = 0.0; x < 10.0; x += 0.1) {
printf("%0.2f %0.2f\n", x, call_func(pow_func,x,2.0));
}
/* Done */
Py_DECREF(pow_func);
Py_Finalize();
return 0;
}
要构建例子代码,你需要编译 C 并将它链接到 Python 解释器。 下面的 Makefile 可以教你怎样做(不过在你机器上面需要一些配置)。
all::
cc -g embed.c -I/usr/local/include/python3.3m \
-L/usr/local/lib/python3.3/config-3.3m -lpython3.3m
double call_func(PyObject *func, double x, double y) {
// ...
/* Verify that func is a proper callable */
if (!PyCallable_Check(func)) {
fprintf(stderr,"call_func: expected a callable\n");
goto fail;
}
// ...
在 C 代码里处理错误你需要格外的小心。一般来讲,你不能仅仅抛出一个 Python 异常。 错误应该使用 C 代码方式来被处理。在这里,我们打算将对错误的控制传给一个叫 abort() 的错误处理器。 它会结束掉整个程序,在真实环境下面你应该要处理的更加优雅些(返回一个状态码)。 你要记住的是在这里 C 是主角,因此并没有跟抛出异常相对应的操作。 错误处理是你在编程时必须要考虑的事情。
最后一个问题是对于 Python 全局锁的管理。 在 C 语言中访问 Python 的时候,你需要确保 GIL 被正确的获取和释放了。 不然的话,可能会导致解释器返回错误数据或者直接奔溃。 调用 PyGILState_Ensure() 和 PyGILState_Release() 可以确保一切都能正常。
double call_func(PyObject *func, double x, double y) {
// ...
double retval;
/* Make sure we own the GIL */
PyGILState_STATE state = PyGILState_Ensure();
// ...
/* Code that uses Python C API functions */
// ...
/* Restore previous GIL state and return */
PyGILState_Release(state);
return retval;
fail:
PyGILState_Release(state);
abort();
}