C++调用python(一)
目录
- 一、基本使用方法
- 二、调用简单语句
正文
一、基本使用方法
二、调用简单语句
三、调用函数
四、调用类
五、调用SSD目标检测算法
六、遇到的错误
最近训练一个3D分割的模型,需要将其结合到项目中,由于项目是C++开发,而这边python训练好的模型尝试了ONNX、libtorch等转换C++也没有成功,因此考虑采用C++直接调用python代码,这里对里面用到的一些方法做一个总结,方便以后查看。
回到顶部
1.1 调用步骤
- 将数据值从C/C++转换为Python
- 使用转换后的值对Python接口例程执行函数调用
- 将Python调用中的数据值转换为C/C++
1.2 编译链接
使用python提供的C/C++接口,需要包含python安装目录下的头文件Python.h 编译、链接时需要指定头文件、python库的地址。
示例:
include: /home/zjh/anaconda3/envs/learn/include/python3.6m Python.h
lib: /home/zjh/anaconda3/envs/learn/lib/ libpython3.6m.a/libpython3.6m.so
注:如果需要Numpy
库的话需要找到numpy的地址,一般存放于第三方库路径下,如:
/home/zjh/anaconda3/envs/learn/lib/python3.6/site-packages/numpy/core/include/numpy arrayobject.h
1.3 基本接口
- 解释器
在C/C++调用python之前必须为其指定解释器环境。
void Py_Initialize():
初始化python解释器.C/C++中调用Python之前必须先初始化解释器
int Py_IsInitialized():
返回python解析器的是否已经初始化完成,如果已完成,返回大于0,否则返回0
void Py_Finalize() :
撤销Py_Initialize()和随后使用Python/C API函数进行的所有初始化,
并销毁自上次调用Py_Initialize()以来创建并未被销毁的所有子解释器。
格式转换
在C/C++中,所有的Python类型都被声明为PyObject类型,为了让C/C++能够操作python的数据,python提供了C语言数据类型到PyObject类型的转换接口。- 数字/字符串
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。 i(int)[int] 将普通的C int转换为Python整数对象。 ...
- 列表
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()函数。
- 元组
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:新元组的大小
- 字典
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:要进行操作的字典。
返回值解析
python执行完返回的结果也是PyObject类型,因此需要将PyObject类型转换为C/C++类型。
int PyArg_Parse( PyObject *args, char *format, ...)
根据format把args的值转换成c类型的值,[format](https://docs.python.org/3/c-api/arg.html)接受的类型和上述Py_BuildValue()的是一样的,
- 释放资源
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指针,否则将导致错误。
回到顶部
二、调用简单语句
首先编写CMakeLists.txt
文件,引入python所需头文件和库文件,python版本是3.6.5
。
- CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(helloworld)
set(SDK_VERSION 0_0_1)
# >>> build type
set(CMAKE_BUILD_TYPE "Release")# 指定生成的版本
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
# <<<
# >>> CXX11
set(CMAKE_CXX_STANDARD 11)# C++ 11 编译器
SET(CMAKE_CXX_STANDARD_REQUIRED TRUE)
# <<<
# >>> Python3
set(PYTHON_ROOT "/home/zjh/anaconda3/envs/learn")
message("python root: " ${PYTHON_ROOT})
include_directories(${PYTHON_ROOT}/include/)
link_directories(${PYTHON_ROOT}/lib/)
# <<<
# --- generate ---
add_executable(helloworld helloworld.cpp)
target_link_libraries(helloworld -lpython3.6m)
- helloworld.cpp
#include <python3.6m/Python.h>
int main(int argc, char *argv[])
{
wchar_t *program = Py_DecodeLocale(argv[0], nullptr);
if ( program == nullptr ){
std::cout << "Fatal Error: cannot decode argv[0]!" << std::endl;
return -1;
}
Py_SetProgramName(program);
Py_Initialize(); ## 初始化
PyRun_SimpleString("print('hello world!')");
Py_Finalize(); ## 释放资源
PyMem_RawFree(program);
return 0;
}
将CMakeLists.txt
和helloworld.cpp
两个文件放在同一文件夹下,在新建build
文件夹,进行编译构建,构建成功后目录下会生成helloworld
可执行文件。
$ cmake..
$ make
参考链接:
https://docs.python.org/2/extending/embedding.html
https://zhuanlan.zhihu.com/p/79896193
https://blog.csdn.net/ziweipolaris/article/details/83689597
https://blog.csdn.net/u011681952/article/details/92765549
https://blog.csdn.net/hnlylyb/article/details/89498651
版权声明
本文仅代表作者观点,不代表xx立场。
本文系作者授权xx发表,未经许可,不得转载。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。