Python作为一门流行通用的脚本语言,可以很好的和C/C++程序结合在一起。 在一个C/C++应用程序中,我们可以用一组插件来实现一些具有统一接口的功能,一般插件都是使用动态链接库实现,如果插件的变化比较频繁,我们可以使用Python来代替动态链接库形式的插件,这样可以很方便地更具需要求的变化改写脚本代码(进行不同的数据处理),而不是必须重新编译链接二进制的动态链接库。
在一个C/C++程序中使用Python程序有两种方式: 1. 使用使用一个可变的Python字符串,通过Python引擎执行 2. 构造一个Python模块,根据类,方法,构造参数来调用
除了这些使用方式上的不同,基本的执行调度如下所示: 1. 初始化Python解释器的实例 2. 执行Python代码 3. 释放Python解释器
Python C API基本方法如下所示(第一列对应C API的使用,第二列对应Python中的使用):
C API | Python | 描述 |
---|---|---|
PyImport_ImportModule | import module | |
PyImport_ReloadModule | reload(module) | |
PyImport_GetModuleDict | sys.modules | |
PyModule_GetDict | module.__dict__ | |
PyDict_GetItemString | dict[key] | |
PyDict_SetItemString | dict[key] = value | |
PyDict_New | dict = {} | |
PyObject_GetAttrString | getattr(obj, attr) | |
PyObject_SetAttrString | setattr(obj, attr, val) | |
PyEval_CallObject | apply(function, args) | 调用函数,其中args是元组 |
PyRunString | eval(expr), exec expr | expr被当作python语句执行 |
PyRun_File | execfile(filename) | 执行文件 |
PySetProgramName | sys.argv[0] | |
PyGetProgramName | sys.argv[0] | |
PySys_SetArgv | sys.argv |
exec语句用来执行保存在字符串或是文件中的Python语句,例如:
>>> exec 'print "Hello World"'
eval语句用来计算保存在字符串中的有效Python表达式,例如:
>>> eval_r('2*3')
两者的区别就是,eval把字符串当成有效的Python表达式来求值,并返回计算结果。 execfile用来执行一个文件,例如:
>>> execfile(r'd:\code\ex\test.py')
helloworld.cpp:
#include "python2.6/Python.h"
int main() {
Py_Initialize();
const char * cstr_cmd = "sys.path.append('./')";
PyRun_SimpleString("import sys");
PyRun_SimpleString(cstr_cmd);
PyRun_SimpleString("import helloworld");
PyRun_SimpleString("helloworld.test()");
Py_Finalize();
return 0;
}
helloworld.py
#!/usr/bin/python
# encoding: utf-8
def test():
print "helloworld"
在C/C++程序中可使用printf以及scanf函数作为输入和输出,而在Python C API中使用PyArg_Parse*形式的函数来将Python 对象转换成对应的C类型。
例如,你可以使用PyArg_ParseTuple函数来获取元组中的数据,如下所示:
float radius, height;
PyArg_ParseTuple(args,"ff",&radius,&height);
其中第一个参数就是Python中的元组对象,第二个参数是format,这里使用两个f表示是两个浮点数类型的数据,最后两个参数指向需要加载数据的地址。
当然,你也可以使用Py_BuildValue来构造Python对象,例如创建一个指向浮点数的Python对象:
PyObject pyfloat;
float pi = 3.141592654;
pyfloat = Py_BuildValue("f",pi);
常用的数据类型转换函数如下所示:
1、数字与字符串
Py_BuildValue()函数的作用和PyArg_ParseTuple()的作用相反,它是将C类型的数据结构转换成Python对象,该函数的原型如下:
PyObject *Py_BuildValue(char *format, ...)
参数列表中剩余的参数(即整型、浮点型或者字符串等,不能为指针)会被转换成对应的PyObject对象。
示例如下:
Py_BuildValue("") None
Py_BuildValue("i", 123) 123
Py_BuildValue("iii", 123, 456, 789) (123, 456, 789)
Py_BuildValue("s", "hello") 'hello'
Py_BuildValue("ss", "hello", "world") ('hello', 'world')
Py_BuildValue("s#", "hello", 4) 'hell'
Py_BuildValue("()") ()
Py_BuildValue("(i)", 123) (123,)
Py_BuildValue("(ii)", 123, 456) (123, 456)
Py_BuildValue("(i,i)", 123, 456) (123, 456)
Py_BuildValue("[i,i]", 123, 456) [123, 456]
Py_BuildValue("{s:i,s:i}", "abc",
123, "def", 456) {'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii)) (ii)",
1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))
2、列表
PyList_New()函数用来创建一个新的Python列表。函数原型如下:
PyObject* PyList_New( Py_ssize_t len)
其中参数len表示创建的列表长度,列表创建之后,可以使用PyList_SetItem()函数向列表中添加item,函数原型如下所示:
int PyList_SetItem( PyObject *list, Py_ssize_t index, PyObject *item)
其中参数list表示列表对象,index表示位置索引,item表示item对象
当然还有其他的一些C API支持列表操作,如PyList_GetItem、PyList_Append、PyList_Sort、PyList_Reverse等,就不一一列出了,详细可参考Python官方文档。
3、元组
PyTuple_New()函数用来创建一个新的Python元组。函数原型如下:
PyObject* PyTuple_New( Py_ssize_t len)
其中参数len表示创建的元组长度,元组创建之后,可以使用PyTuple_SetItem()函数向元组中添加item,函数原型如下所示:
int PyTuple_SetItem( PyObject *p, Py_ssize_t pos, PyObject *o)
其中参数p表示元组对象,pos表示位置索引,o表示item对象
当然还有其他的一些C API支持元组操作,如PyTuple_Resize等。
4、字典
PyDict_New()函数用来创建一个新的Python字典。函数原型如下:
PyObject* PyDict_New()
字典创建之后,可以使用PyDict_SetItem()函数和PyDict_SetItemString()函数向字典中添加item。函数原型分别如下所示:
int PyDict_SetItem( PyObject *p, PyObject *key, PyObject *val)
int PyDict_SetItemString( PyObject *p, const char *key, PyObject *val)
当然还有其他的一些C API支持字典操作,如PyDict_DelItem、PyDict_DelItemString、PyDict_Next(遍历字典)、PyDict_Items、PyDict_Keys等。
如果想过使得Python代码工作并且从Python解释器中中获得代码执行之后的一些结果信息,那么结合Python对象使用可能更加方便。通过使用不同类型的Python对象,能够获取任意属性以及相关Python对象的引用。
首先,需要创建一个module。这里我们以reverse为示例,主要的目的就是用来对一些数据类型进行倒置,reverse module如下所示:
def rstring(s):
i = len(s)-1
t = ''
while(i > -1):
t += s[i]
i -= 1
return t
def rnum(i):
return 1.0/float(i)
def rlist(l):
l.reverse()
return l
def rdict(d):
e = {}
for k in d.keys():
e[d[k]] = k
return e
为了使用其中的一个函数在一个C/C++程序中,需要访问对象的引用。PyObject_GetAttrString()函数可以通过name获得对象中的属性。这里假设我们需要访问rstring函数,也就是module reverse对象的一个属性。执行如下的步骤可以调用rstring: 1. 引入包含被调函数的module 2. 获得访问module中属性函数的引用 3. 执行函数 4. 函数返回值转化为C/C++程序中的变量
reverse.cpp示例程序如下所示:
#include "python2.6/Python.h"
#include <stdio.h>
int main() {
PyObject *strret, *mymod, *strfunc, *strargs;
char *cstrret;
Py_Initialize();
const char * cstr_cmd = "sys.path.append('./')";
PyRun_SimpleString("import sys");
PyRun_SimpleString(cstr_cmd);
mymod = PyImport_ImportModule("reverse");
strfunc = PyObject_GetAttrString(mymod, "rstring");
strargs = Py_BuildValue("(s)", "Hello World");
strret = PyEval_CallObject(strfunc, strargs);
PyArg_Parse(strret, "s", &cstrret);
printf("Reversed string: %s\n", cstrret);
Py_Finalize();
return 0;
}
Python是一门面向对象的语言,大部分与Python的交互都会和Python classes以及objects相关。在C/C++程序中访问类和上面访问函数基本的步骤类似。这里我们以一个摄氏温度转华氏温度为例,class如下所示:
class celsius:
def __init__(self, degrees):
self.degrees = degrees
def farenheit(self):
return ((self.degrees*9.0)/5.0)+32.0
Python程序中使用该类一般方法如下所示:
import celsius
temp = celsius.celsius(100)
print temp.farenheit()
C++示例程序如下所示:
#include <python2.6/Python.h>
#include <stdio.h>
/* Create a function to handle errors when they occur */
void error(const char *errstring) {
printf("%s\n", errstring);
exit(1);
}
int main() {
PyObject *ret, *mymod, *cls, *method, *args, *object;
float farenheit;
Py_Initialize();
/* Add the current path */
const char * cstr_cmd = "sys.path.append('./')";
PyRun_SimpleString("import sys");
PyRun_SimpleString(cstr_cmd);
mymod = PyImport_ImportModule("celsius");
if (mymod == NULL)
error("Can't open module");
/* Find the class */
cls = PyObject_GetAttrString(mymod, "celsius");
/* If found the class we can dump mymod, since we won't use it again */
Py_DECREF (mymod);
/* Check to make sure we got an object back */
if (cls == NULL) {
Py_DECREF(cls);
error("Can't find class");
}
args = Py_BuildValue("(f)", 100.0);
if (args == NULL) {
Py_DECREF (args);
error("Can't build argument list for class instance");
}
/* Create a new instance of our class by calling the class with our argument list */
object = PyEval_CallObject(cls, args);
if (object == NULL) {
Py_DECREF (object);
error("Can't create object instance");
}
/* Decrement the argument counter as we'll be using this again */
Py_DECREF (args);
/* Get the object method - note we use the object as the object
* from which we access the attribute by name, not the class */
method = PyObject_GetAttrString(object, "farenheit");
if (method == NULL) {
Py_DECREF (method);
error("Can't find method");
}
/* Decrement the counter for our object, since we now just need the method reference */
Py_DECREF (object);
/* Build our argument list - an empty tuple because there aren't any arguments */
args = Py_BuildValue("()");
if (args == NULL) {
Py_DECREF(args);
error("Can't build argument list for method call");
}
/* Call our object method with arguments */
ret = PyEval_CallObject(method, args);
if (ret == NULL) {
Py_DECREF (ret);
error("Couldn't call method");
}
/* Convert the return value back into a C variable and display it */
PyArg_Parse(ret, "f", &farenheit);
printf("Farenheit: %f\n", farenheit);
/* Kill the remaining objects we don't need */
Py_DECREF (method);
Py_DECREF (ret);
/* Close off the interpreter and terminate */
Py_Finalize();
return 0;
}
该示例和之前给出的示例不同的地方在于,加入了一些错误检查以及对于已经创建的但是后续不再使用的Python对象及时释放掉。
释放对象引用有Py_DECREF以及Py_XDECREF两种方式,前者必须保证对象存在即(不为NULL),后者如果对象不存在会直接忽略。上面的示例程序会发生segment fault的错误,因此建议使用Py_XDECREF来释放Python对象。
当然,上述示例程序除了Py_DECREF之外还存在很多安全问题,大家在写的时候可以加入一下安全检查机制确保程序能够正常执行,比如: 1. 获得函数对象引用之后使用PyCallable_Check验证函数是否是可执行的 2. 完成函数调用之后使用PyErr_Occurred检查是否有异常发生 3. 检查返回的结果类型是否符合要求,PyFloat_Check等
https://www6.software.ibm.com/developerworks/education/l-pythonscript/l-pythonscript-ltr.pdf
https://github.com/yidao620c/python3-cookbook/blob/master/source/c15/p06_calling_python_from_c.rst
http://blog.sina.com.cn/s/blog_76e94d210100w1bl.html