『Python CoolBook』C扩展库_其六_从C语言中调用Python代码
一、C语言运行pyfun的PyObject对象
思路是在C语言中提供实参,传给python函数:
- 获取py函数对象(PyObject),函数参数(C类型)
- 获取GIL(PyGILState_Ensure)
- 确保fun对象可调用
- 参数转换为python对应类型(Py_BuildValue)
- 调用python函数(PyObject_Call)
- 确定调用无异常
- 检查返回值
- 释放GIL(PyGILState_Release)
- 异常处理
#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 */ /* 你必须先有一个表示你将要调用的Python可调用对象。 这可以是一个函数、 类、方法、内置方法或其他任意实现了 __call__() 操作的东西。 为了确 保是可调用的,可以像下面的代码这样利用 PyCallable_Check() 做检查 */ if (!PyCallable_Check(func)) { fprintf(stderr,"call_func: expected a callable\n"); goto fail; } /* Build arguments */ /* 使用 Py_BuildValue()构建参数元组或字典 */ args = Py_BuildValue("(dd)", x, y); kwargs = NULL; /* Call the function */ /* 使用 PyObject_Call(),传一个可调用对象给它、一个参数元组 和一个可选的关键字字典。 如果没有关键字参数,传递NULL */ result = PyObject_Call(func, args, kwargs); /* 需要确保使用了 Py_DECREF() 或者 Py_XDECREF() 清理参数。 第二个函数相对安全点,因为它允许传递NULL指针(直接忽略它), 这也是为什么我们使用它来清理可选的关键字参数。 */ Py_DECREF(args); Py_XDECREF(kwargs); /* Check for Python exceptions (if any) */ /* 调用万Python函数之后,用PyErr_Occurred() 函数检查是否 有异常发生 */ 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 }
要注意的是每一个 PyGILState_Ensure()
调用必须跟着一个匹配的 PyGILState_Release()
调用——即便有错误发生。 在这里,我们使用一个 goto
语句看上去是个可怕的设计, 但是实际上我们使用它来讲控制权转移给一个普通的exit块来执行相应的操作。 在 fail:
标签后面的代码和Python的 fianl:
块的用途是一样的。
二、使用模块名和方法名获取pyfun的PyObject对象
- 获取模块名字符串,方法名字符串
- 模块名转化为python的字符串类型(PyUnicode_FromString)
- 模拟python的import行为(PyImport_Import),这是因为我们想经由python的逻辑获取函数
- 由python的module获取方法(PyObject_GetAttrString),这个API获取方法使用的是C字符串
- 返回方法,时python的对象类型
/* 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); }
三、C模拟Python运行
- 初始化python环境(Py_Initialize)
- 导入模块获取方法(见本文第二部分)为PyObject
- 调用方法PyObject(见本文第一部分)
- 结束python环境(Py_Finalize)
/* 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; }
编译运行,
gcc -g embed.c -I/home/hellcat/anaconda3/include/python3.6m -L/home/hellcat/anaconda3/lib/python3.6/config-3.6m-x86_64-linux-gnu -lpython3.6m
四、将可调用PyObject用C重新封装调用
这是个意义不大功能,只是展示C API中PyObject本质运行逻辑——PyObject可以代指任何Python中的对象,这里是它接收函数的例子:
/* Extension function for testing the C-Python callback */ static PyObject *py_call_func(PyObject *self, PyObject *args) { PyObject *func; double x, y, result; if (!PyArg_ParseTuple(args,"Odd", &func,&x,&y)) { return NULL; } result = call_func(func, x, y); return Py_BuildValue("d", result); }
把它写到前一节中的pysample.c中,有如下效果
>>> import sample
>>> def add(x,y):
... return x+y
...
>>> sample.call_func(add,3,4)
7.0
>>>