随着机器学习/深度学习这几年的的火热,python成了当红炸子鸡,使用python训练机器学习模型则成了开发人员们最喜欢的方法,但是由于过往调度系统一般都是用C++来开发的,因此我们只有两种方法来调用python脚本,一种是使用上篇中提到的子进程的方法,另外一种则是直接使用C++/python进行混合编程。
基本使用方法
python 提供了一套 C API库,使得开发者能很方便地从C/ C++ 程序中调用 Python 模块。具体的文档参考官方指南
至于要使用这个套API,我们需要引入一个头文件和一个库文件,以linux系统自带的python 2.7为例,python 的头文件位于/usr/include/python2.7,库文件为 libpython2.7.so ,在makefile中需要增加
-I /usr/include/python2.7 -l python2.7
我们现在知道了python api的头文件和库文件了,那这些接口是怎样用的呢?下面我们按照不同的主题来拆解下。
初始化Python解释器环境
python是一门解释语言,没有解释器是没法运行的。因此,我们需要为python提供一个解释器环境,和环境相关的接口如下
void Py_Initialize():
初始化python解释器.C/C++中调用Python之前必须先初始化解释器
int Py_IsInitialized():
返回python解析器的是否已经初始化完成,如果已完成,返回大于0,否则返回0
void Py_Finalize() :
撤销Py_Initialize()和随后使用Python/C API函数进行的所有初始化,
并销毁自上次调用Py_Initialize()以来创建并为被销毁的所有子解释器。
调用Python脚本
在C++中要使用要调用python脚本,python api提供了几种方式,下面拿两个api为例,具体的参考官方文档这里和这里
int PyRun_SimpleString(const char*) :
执行一个简单的执行python脚本命令的函数
int PyRun_SimpleFile(FILE *fp, const char *filename):
从fp中把python脚本的内容读取到内容中并执行,filename应该为fp对应的文件名
举个简单的例子
#include "python2.7/Python.h"
int main()
{
Py_Initialize(); ## 初始化
PyRun_SimpleString("print 'hello'");
Py_Finalize(); ## 释放资源
}
在上面例子中,我们把python代码当作一个字符串传给解释器来执行,这咋一看貌似没有什么问题,但是,既然我们选择了使用python,却把python脚本写到C++代码里面,这似乎有点买椟还珠。同时,不管是字符串还是文件,都只能用于c++不需要像python传参,同时python不会向c++返回值的情况,只执行固定脚本的场景,但是,实际的场景中是必然存在C++向python传参,python返回结果的,这个需求下我们要怎样做呢?别急,我们一步步来说。
动态加载python模块并执行函数
先来说说这个问题的解法吧。python api提供了动态加载模块并且执行函数的能力,具体会涉及到下面几个api
//加载模块
PyObject* PyImport_ImportModule(char *name)
PyObject* PyImport_Import(PyObject *name)
PyObject* PyString_FromString(const char*)
上面两个api都是用来动态加载python模块的。区别在于前者一个使用的是C的字符串,而后者的name是一个python对象,
这个python对象需要通过PyString_FromString(const char*)来生成,其值为要导入的模块名
//导入函数相关
PyObject* PyModule_GetDict( PyObject *module)
PyModule_GetDict()函数可以获得Python模块中的函数列表。PyModule_GetDict()函数返回一个字典。字典中的关键字为函数名,值为函数的调用地址。
字典里面的值可以通过PyDict_GetItemString()函数来获取,其中p是PyModule_GetDict()的字典,而key则是对应的函数名
PyObject* PyObject_GetAttrString(PyObject *o, char *attr_name)
PyObject_GetAttrString()返回模块对象中的attr_name属性或函数,相当于Python中表达式语句:o.attr_name
//调用函数相关
PyObject* PyObject_CallObject( PyObject *callable_object, PyObject *args)
PyObject* PyObject_CallFunction( PyObject *callable_object, char *format, ...)
使用上面两个函数可以在C程序中调用Python中的函数。callable_object为要调用的函数对象,也就是通过上述导入函数得到的函数对象,
而区别在于前者使用python的tuple来传参,后者则使用类似c语言printf的风格进行传参。
如果不需要参数,那么args可能为NULL。返回成功时调用的结果,或失败时返回NULL。
这相当于Python表达式 apply(callable_object, args) 或 callable_object(*args)
api知道了,但是怎样用起来呢?我们先来看一个简单的例子。假设我们有这样一个python脚本
#cat script/sayHello.py
def say():
print("hello")
我们可以像下面这样去加载模块并且调用指定的函数
#include <python2.7/Python.h>
#include <iostream>
using namespace std;
int main(){
Py_Initialize();
if( !Py_IsInitialized()){
cout << "python init fail" << endl;
return 0;
}
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./script')");
PyObject* pModule = PyImport_ImportModule("sayHello");
if( pModule == NULL ){
cout <<"module not found" << endl;
return 1;
}
PyObject* pFunc = PyObject_GetAttrString(pModule, "say");
if( !pFunc || !PyCallable_Check(pFunc)){
cout <<"not found function add_num" << endl;
return 0;
}
PyObject_CallObject(pFunc, NULL );
Py_Finalize();
return 0;
}
但是,同学们应该看到,上面的函数是没有参数而且没有返回值的,如果有参数和返回值要怎样做呢?
调用参数
在C/C++中,所有的Python类型都被声明为PyObject型,为了能够让C++能够操作python的数据,python提供了python各种数据类型和C语言数据类型的转换操作,具体的使用方法如下
1.数字与字符串
在Python/C API中提供了Py_BuildValue()函数对数字和字符串进行转换处理,使之变成Python中相应的数据类型。其函数原型如下所示
PyObject* Py_BuildValue( const char *format, ...)
Py_BuildValue()提供了类似c语言printf的参数构造方法,format是要构造的参数的类型列表,函数中剩余的参数即要转换的C语言中的整型、浮点型或者字符串等。
其返回值为PyObject型的指针。
format对应的类型列表如下
s(str或None)[char *]
使用'utf-8'编码将以null结尾的C字符串转换为Python str对象。如果C字符串指针为NULL,则表示None。
s#(str或None)[char *,int]
使用'utf-8'编码将C字符串及其长度转换为Python str对象。如果C字符串指针为NULL,则忽略长度返回None。
y(字节)[char *]
这会将C字符串转换为Python字节对象。如果C字符串指针为NULL,则返回None。
y#(字节)[char *,int]
这会将C字符串及其长度转换为Python对象。如果C字符串指针为NULL,则返回None。
z(str或None)[char *]
与s相同。
z#(str或None)[char *,int]
与s#相同。
u(str)[Py_UNICODE *]
将Unicode(UCS-2或UCS-4)数据的以null结尾的缓冲区转换为Python Unicode对象。如果Unicode缓冲区指针为NULL,则返回None。
u#(str)[Py_UNICODE *,int]
将Unicode(UCS-2或UCS-4)数据缓冲区及其长度转换为Python Unicode对象。如果Unicode缓冲区指针为NULL,则忽略长度并返回None。
U(str或None)[char *]
与s相同。
U#(str或None)[char *,int]
与s#相同。
i(int)[int]
将普通的C int转换为Python整数对象。
b(int)[char]
将纯C char转换为Python整数对象。
h(int)[short int]
将普通的C short int转换为Python整数对象。
l(int)[long int]
将C long int转换为Python整数对象。
B(int)[unsigned char]
将C unsigned char转换为Python整数对象。
H(int)[unsigned short int]
将C unsigned short int转换为Python整数对象。
I(int)[unsigned int]
将C unsigned int转换为Python整数对象。
k(int)[unsigned long]
将C unsigned long转换为Python整数对象。
L(int)[long long]
将C long long转换为Python整数对象。
K(int)[unsigned long long]
将C unsigned long long转换为Python整数对象。
n(int)[Py_ssize_t]
将C Py_ssize_t转换为Python整数。
c(长度为1的字节)[char]
将表示字节的C int转换为长度为1的Python字节对象。
C(长度为1的str)[int]
将表示字符的C int转换为长度为1的Python str对象。
d(float) [double]
将C double转换为Python浮点数。
f(float) [float]
将C float转换为Python浮点数。
D(complex) [Py_complex *]
将C Py_complex结构转换为Python复数。
O(object) [PyObject *]
不改变Python对象的传递(引用计数除外,它增加1)。如果传入的对象是NULL指针,则假定这是因为产生参数的调用发现错误并设置了异常。
因此,Py_BuildValue()将返回NULL但不会引发异常。如果尚未引发异常,则设置SystemError。
S(object) [PyObject *]
与O相同
N((object) [PyObject *]
与O相同,但不会增加对象的引用计数。通过调用参数列表中的对象构造函数创建对象时很有用。
O&(object) [converter, anything]
通过转换器函数将任何内容转换为Python对象。该函数被调用任何东西(应与void *兼容)作为其参数,并应返回“新”Python对象,如果发生错误则返回NULL。
(items) (tuple) [matching-items]
将一系列C值转换为具有相同项目数的Python元组。
[items](list) [matching-items]
将一系列C值转换为具有相同项目数的Python列表。
{items}(dict) [matching-items]
将一系列C值转换为Python字典。每对连续的C值将一个项添加到字典中,分别用作键和值。
如果格式字符串中存在错误,则设置SystemError异常并返回NULL。
2.列表
PyObject* PyList_New( Py_ssize_t len)
创建一个新的Python列表,len为所创建列表的长度
int PyList_SetItem( PyObject *list, Py_ssize_t index, PyObject *item)
向列表中添加项。当列表创建以后,可以使用PyList_SetItem()函数向列表中添加项。 list:要添加项的列表。 index:所添加项的位置索引。 item:所添加项的值。
PyObject* PyList_GetItem( PyObject *list, Py_ssize_t index)
获取列表中某项的值。list:要进行操作的列表。index:项的位置索引。
Py_ssize_t PyList_Size(PyObject * list)
返回列表中列表对象的长度;这相当于列表对象上的 len(list) 。
int PyList_Append( PyObject *list, PyObject *item)
int PyList_Sort( PyObject *list)
int PyList_Reverse( PyObject *list)
Python/C API中提供了与Python中列表操作相对应的函数。例如
列表的append方法对应于PyList_Append()函数。
列表的sort方法对应于PyList_Sort()函数。
列表的reverse方法对应于PyList_Reverse()函数。
3.元组
PyObject* PyTuple_New( Py_ssize_t len)
PyTuple_New()函数返回所创建的元组。其函数原型如下所示。len:所创建元组的长度。
int PyTuple_SetItem( PyObject *p, Py_ssize_t pos, PyObject *o)
当元组创建以后,可以使用PyTuple_SetItem()函数向元组中添加项。p:所进行操作的元组,pos:所添加项的位置索引,o:所添加的项值。
PyObject* PyTuple_GetItem( PyObject *p, Py_ssize_t pos)
可以使用Python/C API中PyTuple_GetItem()函数来获取元组中某项的值。p:要进行操作的元组,pos:项的位置索引
Py_ssize_t PyTuple_Size(PyObject * p)
获取指向元组对象的指针,并返回该元组的大小。
int _PyTuple_Resize( PyObject **p, Py_ssize_t newsize)
当元组创建以后可以使用_PyTuple_Resize()函数重新调整元组的大小。其函数原型如下所示。p:指向要进行操作的元组的指针,newsize:新元组的大小
4.字典
PyObject* PyDict_New()
PyDict_New()函数返回所创建的字典。
int PyDict_SetItem( PyObject *p, PyObject *key, PyObject *val)
int PyDict_SetItemString( PyObject *p, const char *key, PyObject *val)
当字典创建后,可以使用PyDict_SetItem()函数和PyDict_SetItemString()函数向字典中添加项。 其参数含义如下。
p:要进行操作的字典。key:添加项的关键字,
对于PyDict_SetItem()函数其为PyObject型,
对于PyDict_SetItemString()函数其为char型,val:添加项的值。
PyObject* PyDict_GetItem( PyObject *p, PyObject *key)
PyObject* PyDict_GetItemString( PyObject *p, const char *key)
使用Python/C API中的PyDict_GetItem()函数和PyDict_GetItemString()函数来获取字典中某项的值。它们都返回项的值。
其参数含义如下。p:要进行操作的字典,key:添加项的关键字,
对于PyDict_GetItem()函数其为PyObject型
对于PyDict_GetItemString()函数其为char型。
PyObject* PyDict_Items( PyObject *p)
PyObject* PyDict_Keys( PyObject *p)
PyObject* PyDict_Values( PyObject *p)
在Python/C API中提供了与Python中字典操作相对应的函数。例如
字典的item方法对应于PyDict_Items()函数。
字典的keys方法对应于PyDict_Keys()函数。
字典的values方法对应于PyDict_Values()函数。
其参数p:要进行操作的字典。
但是上面所有操作都是基于C语言的,至于像c++的string以及各种容器类型,则需要自己通过封装来实现了。例如下面这样
// PythonParam.h
#ifndef __PYTHON_CPP_PYTHON_PARAM_H__
#define __PYTHON_CPP_PYTHON_PARAM_H__
#include <map>
#include <string>
#include <vector>
#include <python2.7/Python.h>
class PythonParamBuilder{
public:
PythonParamBuilder();
~PythonParamBuilder();
PyObject* Build();
bool AddString( const std::string& );
bool AddList( const std::vector<std::string>& params );
bool AddMap( const std::map<std::string, std::string>& mapParam);
template<typename T>
bool AddCTypeParam( const std::string& typeStr, T v ){
PyObject * param = Py_BuildValue( typeStr, v );
return AddPyObject( param );
}
private:
bool AddPyObject( PyObject* obj );
PyObject* ParseMapToPyDict(const std::map<std::string, std::string>& mapParam);
PyObject* ParseListToPyList(const std::vector<std::string>& params);
PyObject* ParseStringToPyStr( const std::string& str );
private:
PyObject* mArgs;
std::vector<PyObject*> mTupleObjects;
};
#endif
//PythonParam.cpp
#include "PythonParam.h"
PythonParamBuilder::PythonParamBuilder()
:mArgs(nullptr){
}
PythonParamBuilder::~PythonParamBuilder(){
for ( std::vector<PyObject*>::iterator it = mTupleObjects.begin();
it != mTupleObjects.end();
++it ){
if( *it )
Py_DECREF( *it );
}
if ( mArgs )
Py_DECREF(mArgs);
}
PyObject* PythonParamBuilder::Build(){
if ( mTupleObjects.empty() )
return nullptr;
mArgs = PyTuple_New( mTupleObjects.size() );
for ( int i=0; i<mTupleObjects.size(); i++){
PyTuple_SetItem(mArgs, i, mTupleObjects[i] );
}
return mArgs;
}
bool PythonParamBuilder::AddPyObject( PyObject* obj ){
if ( nullptr == obj )
return false;
mTupleObjects.push_back(obj);
return true;
}
bool PythonParamBuilder::AddString( const std::string& str ){
return AddPyObject( ParseStringToPyStr( str ) );
}
bool PythonParamBuilder::AddList( const std::vector<std::string>& params ){
return AddPyObject( ParseListToPyList(params) );
}
bool PythonParamBuilder::AddMap( const std::map<std::string, std::string>& mapParam){
return AddPyObject( ParseMapToPyDict( mapParam ) );
}
PyObject* PythonParamBuilder::ParseMapToPyDict(const std::map<std::string, std::string>& mapParam){
PyObject* pyDict = PyDict_New();
for (std::map<std::string, std::string>::const_iterator it = mapParam.begin();
it != mapParam.end();
++it) {
PyObject* pyValue = PyString_FromString(it->second.c_str());
if (pyValue == NULL) {
printf( "Parse param:[%s] to PyStringObject failed.", it->second.c_str());
return NULL;
}
if (PyDict_SetItemString(pyDict, it->first.c_str(), pyValue) < 0) {
printf( "Parse key:[%s] value:[%s] failed.", it->first.c_str(), it->second.c_str());
return NULL;
}
}
return pyDict;
}
PyObject* PythonParamBuilder::ParseListToPyList(const std::vector<std::string>& params){
size_t size = params.size();
PyObject* paramList = PyList_New(size);
for (size_t i = 0; i < size; i++) {
PyObject* pyStr = PyString_FromStringAndSize(params[i].data(), params[i].size());
if (pyStr == NULL) {
printf( "Parse param:[%s] to PyStringObject failed.", params[i].c_str());
break;
}
if (PyList_SetItem(paramList, i, pyStr) != 0) {
printf( "param:[%s] append to PyParamList failed.", params[i].c_str());
break;
}
}
return paramList;
}
PyObject* PythonParamBuilder::ParseStringToPyStr( const std::string& str ){
return PyString_FromStringAndSize( str.data(), str.size() );
}
返回值
python函数的返回值也是PyObject类型,因此,在python脚本返回到C/C++之后,需要解构Python数据为C的类型,这样C/C++程序中才可以使用Python里的数据。但是,由于python的返回值有多种数据结构类型,因此,我们需要为每个类型进行转换。不过由于篇幅问题,我们只是介绍简单的整形和字符串类型的处理,其他类型的返回见文末的github链接,总体思路都是根据类型逐个从值从PyObject中提取。python提供了下面函数来完成这个功能
int PyArg_Parse( PyObject *args, char *format, ...)
根据format把args的值转换成c类型的值,format接受的类型和上述Py_BuildValue()的是一样的
释放资源
Python使用引用计数机制对内存进行管理,实现自动垃圾回收。在C/C++中使用Python对象时,应正确地处理引用计数,否则容易导致内存泄漏。在Python/C API中提供了Py_CLEAR()、Py_DECREF()等宏来对引用计数进行操作。
当使用Python/C API中的函数创建列表、元组、字典等后,就在内存中生成了这些对象的引用计数。在对其完成操作后应该使用Py_CLEAR()、Py_DECREF()等宏来销毁这些对象。其原型分别如下所示
void Py_CLEAR(PyObject *o)
void Py_DECREF(PyObject *o)
其中,o的含义是要进行操作的对象。
对于Py_CLEAR()其参数可以为NULL指针,此时,Py_CLEAR()不进行任何操作。而对于Py_DECREF()其参数不能为NULL指针,否则将导致错误。
好了,把各个细节都说了一遍了,下面是另外一个简单的例子,在这个例子中,我们会有输入参数和返回值
/*
cat script/Py2Cpp.py
def add_num(a,b):
return a+b
*/
#include <python2.7/Python.h>
#include <iostream>
using namespace std;
int main(){
Py_Initialize();
if( !Py_IsInitialized()){
cout << "python init fail" << endl;
return 0;
}
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./script')");
PyObject* moduleName = PyString_FromString("Py2Cpp");
PyObject* pModule = PyImport_Import(moduleName);
if( pModule == NULL ){
cout <<"module not found" << endl;
return 1;
}
PyObject* pFunc = PyObject_GetAttrString(pModule, "add_num");
if( !pFunc || !PyCallable_Check(pFunc)){
cout <<"not found function add_num" << endl;
return 0;
}
PyObject* args = Py_BuildValue("(ii)", 28, 103);
PyObject* pRet = PyObject_CallObject(pFunc, args );
Py_DECREF(args);
int res = 0;
PyArg_Parse(pRet, "i", &res );
Py_DECREF(pRet);
cout << res << endl;
Py_Finalize();
return 0;
}
one more thing
C++调用python的过程中,好多地方都需要用到资源的释放,包括解释器的创建和销毁,资源的计数减少等等,这些可以借助C++的RAII来把事情做的更好,具体的封装类见这里。
参考材料:
Python 2.7.15 中文文档 - 1. 用C或C扩展Python | Docs4devwww.docs4dev.com/docs/zh/python/2.7.15/all/extending-extending.html#%E4%BB%8Ec%E8%B0%83%E7%94%A8python%E5%87%BD%E6%95%B0 Python / C API参考手册www.docs4dev.com/docs/zh/python/2.7.15/all/c-api-index.html#python--c-api%E5%8F%82%E8%80%83%E6%89%8B%E5%86%8C 标签:调用,函数,python,Py,Python,C++,PyObject,int From: https://www.cnblogs.com/lidabo/p/17075722.html