C++调用Python

1、Python嵌入程序介绍

为什么要使用Python嵌入程序

Python作为一门流行通用的脚本语言,可以很好的和C/C++程序结合在一起。 在一个C/C++应用程序中,我们可以用一组插件来实现一些具有统一接口的功能,一般插件都是使用动态链接库实现,如果插件的变化比较频繁,我们可以使用Python来代替动态链接库形式的插件,这样可以很方便地更具需要求的变化改写脚本代码(进行不同的数据处理),而不是必须重新编译链接二进制的动态链接库。

Python嵌入的原理

在一个C/C++程序中使用Python程序有两种方式: 1. 使用使用一个可变的Python字符串,通过Python引擎执行 2. 构造一个Python模块,根据类,方法,构造参数来调用

除了这些使用方式上的不同,基本的执行调度如下所示: 1. 初始化Python解释器的实例 2. 执行Python代码 3. 释放Python解释器

2、Python C API基础

Python C API介绍

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')

创建一个C++调用Python解释器执行python语句的程序

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"

3、数据类型转换

在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对象。

  • “s” (string) [char *] :将C字符串转换成Python对象,如果C字符串为空,返回NONE
  • “s#” (string) [char *, int] :将C字符串和它的长度转换成Python对象,如果C字符串为空指针,长度忽略,返回NONE
  • “z” (string or None) [char *] :作用同”s”
  • “z#” (string or None) [char *, int] :作用同”s#”
  • “i” (integer) [int] :将一个C类型的int转换成Python int对象
  • “b” (integer) [char] :作用同”i”
  • “h” (integer) [short int] :作用同”i”
  • “l” (integer) [long int] :将C类型的long转换成Pyhon中的int对象
  • “c” (string of length 1) [char] :将C类型的char转换成长度为1的Python字符串对象
  • “d” (float) [double] :将C类型的double转换成python中的浮点型对象
  • “f” (float) [float] :作用同”d”
  • “O&” (object) [converter, anything] :将任何数据类型通过转换函数转换成Python对象,这些数据作为转换函数的参数被调用并且返回一个新的Python对象,如果发生错误返回NULL
  • “(items)” (tuple) [matching-items] :将一系列的C值转换成Python元组
  • “[items]” (list) [matching-items] :将一系列的C值转换成Python列表
  • “{items}” (dictionary) [matching-items] :将一系类的C值转换成Python的字典,每一对连续的C值将转换成一个键值对

示例如下:

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等。

4、访问Python函数

如果想过使得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;
}

5、访问Python类

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

https://docs.python.org/2.0/ext/buildValue.html

http://www.bozhiyue.com/cloud/2016/0825/432022.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏中国Android研究院

哪些情况下的对象会被垃圾回收机制处理掉

Java的虚拟机本身是蛮复杂的,我们不仔细讲细节。我们只针对我们平时最关心的堆中的哪些对象会被GC回收。我们这样思考,既然GC要回收这块内存,那总得有个方法让G...

3.2K40
来自专栏java技术学习之道

Java关键字new-----对象的内存分配原理

33950
来自专栏用户画像

java的finalize( )方法与C++的析构函数

一、析构函数的作用         析构函数(destructor) 与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函...

10140
来自专栏Ryan Miao

Java8-如何构建一个Stream

Stream的创建方式有很多种,除了最常见的集合创建,还有其他几种方式。 List转Stream List继承自Collection接口,而Collection...

38640
来自专栏智能算法

极简Python入门

本文旨在帮助从总体上帮助了解Python的一些基本属性,具体的使用技巧需要通过不断实践积累 一、Python的基本特性 ? ? ? 二、Python的类 ...

31760
来自专栏debugeeker的专栏

《coredump问题原理探究》Linux x86版7.4节List coredump例子

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/detai...

12130
来自专栏代码世界

Python之编码

一、Python2与Python3的区别 1、从宏观上考虑,Python2重复代码太多,错误率高,不够规范。Python崇尚的是语言简洁、优美、清晰。Pytho...

339100
来自专栏行者常至

谈谈 final、finally、finalize 的区别

12230
来自专栏顶级程序员

说出来你们可能不信,但是数组名确实不是指针常量

这篇是一篇C语言劝退流教学文,看不懂的同学应该是劝退教学流的目标对象 (写完了才想起来注一下,本文提到的“数组”和“数组名”字样,指的是数组左值表达式(arr...

40560
来自专栏大前端_Web

jsvascript—谜之this?

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

18640

扫码关注云+社区

领取腾讯云代金券