前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++调用Python

C++调用Python

作者头像
chain
发布2018-08-02 14:45:36
9.3K0
发布2018-08-02 14:45:36
举报

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语句,例如:

代码语言:javascript
复制
>>> exec 'print "Hello World"'

eval语句用来计算保存在字符串中的有效Python表达式,例如:

代码语言:javascript
复制
>>> eval_r('2*3')

两者的区别就是,eval把字符串当成有效的Python表达式来求值,并返回计算结果。 execfile用来执行一个文件,例如:

代码语言:javascript
复制
>>> execfile(r'd:\code\ex\test.py')

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

helloworld.cpp:

代码语言:javascript
复制
#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

代码语言:javascript
复制
#!/usr/bin/python
# encoding: utf-8

def test():
    print "helloworld"

3、数据类型转换

在C/C++程序中可使用printf以及scanf函数作为输入和输出,而在Python C API中使用PyArg_Parse*形式的函数来将Python 对象转换成对应的C类型。

例如,你可以使用PyArg_ParseTuple函数来获取元组中的数据,如下所示:

代码语言:javascript
复制
float radius, height;
PyArg_ParseTuple(args,"ff",&radius,&height);

其中第一个参数就是Python中的元组对象,第二个参数是format,这里使用两个f表示是两个浮点数类型的数据,最后两个参数指向需要加载数据的地址。

当然,你也可以使用Py_BuildValue来构造Python对象,例如创建一个指向浮点数的Python对象:

代码语言:javascript
复制
PyObject pyfloat;
float pi = 3.141592654;
pyfloat = Py_BuildValue("f",pi);

常用的数据类型转换函数如下所示:

1、数字与字符串

Py_BuildValue()函数的作用和PyArg_ParseTuple()的作用相反,它是将C类型的数据结构转换成Python对象,该函数的原型如下:

代码语言:javascript
复制
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值将转换成一个键值对

示例如下:

代码语言:javascript
复制
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列表。函数原型如下:

代码语言:javascript
复制
PyObject* PyList_New( Py_ssize_t len)

其中参数len表示创建的列表长度,列表创建之后,可以使用PyList_SetItem()函数向列表中添加item,函数原型如下所示:

代码语言:javascript
复制
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元组。函数原型如下:

代码语言:javascript
复制
PyObject* PyTuple_New( Py_ssize_t len)

其中参数len表示创建的元组长度,元组创建之后,可以使用PyTuple_SetItem()函数向元组中添加item,函数原型如下所示:

代码语言:javascript
复制
int PyTuple_SetItem( PyObject *p, Py_ssize_t pos, PyObject *o)

其中参数p表示元组对象,pos表示位置索引,o表示item对象

当然还有其他的一些C API支持元组操作,如PyTuple_Resize等。

4、字典

PyDict_New()函数用来创建一个新的Python字典。函数原型如下:

代码语言:javascript
复制
PyObject* PyDict_New()

字典创建之后,可以使用PyDict_SetItem()函数和PyDict_SetItemString()函数向字典中添加item。函数原型分别如下所示:

代码语言:javascript
复制
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如下所示:

代码语言:javascript
复制
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示例程序如下所示:

代码语言:javascript
复制
#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如下所示:

代码语言:javascript
复制
class celsius:
    def __init__(self, degrees):
        self.degrees = degrees

    def farenheit(self):
        return ((self.degrees*9.0)/5.0)+32.0

Python程序中使用该类一般方法如下所示:

代码语言:javascript
复制
import celsius
temp = celsius.celsius(100)
print temp.farenheit()

C++示例程序如下所示:

代码语言:javascript
复制
#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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年12月17日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、Python嵌入程序介绍
    • 为什么要使用Python嵌入程序
      • Python嵌入的原理
      • 2、Python C API基础
        • Python C API介绍
          • 创建一个C++调用Python解释器执行python语句的程序
          • 3、数据类型转换
          • 4、访问Python函数
          • 5、访问Python类
          • 参考
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档