Linux C++调用Python3
入门
准备
以下面的目录结构演示如何在Linux C/C++调用python3。
|--hello.py
|--main.cpp
|--CMakeLists.txt
- hello.py:python的脚本,里面有2个函数
- main.cpp:c++函数
- CMakeLists.txt:Cmake文件,生成makefile
python脚本
示例python脚本hello.py内容如下,有2个函数:
def add(a,b):
return a+b
def get_name(first):
return "your name is {} alice".format(first)
c++调用python3
#include <iostream>
#include <Python.h>
using namespace std;
const int kError = -1;
const int kSuccess = 0;
int pythonInit() {
//初始化python
Py_Initialize();
int ret = Py_IsInitialized();
if (ret == 0) {
cout << "ocr_card_init Py_Initialize error" << endl;
return kError;
}
return kSuccess;
}
void pythonCleanup() {
Py_Finalize();
}
PyObject *pythonImportModule(const char *pyDir, const char *name) {
//引入当前路径,否则下面模块不能正常导入
char tempPath[256] = {};
sprintf(tempPath, "sys.path.append('%s')", pyDir);
PyRun_SimpleString("import sys");
//PyRun_SimpleString("sys.path.append('./')");
PyRun_SimpleString(tempPath);
PyRun_SimpleString("print('curr sys.path', sys.path)");
//引入模块, hello.py
PyObject *module = PyImport_ImportModule(name);
if (module == nullptr) {
PyErr_Print(); // print stack
cout << "PyImport_ImportModule 'hello.py' not found" << endl;
return nullptr;
}
return module;
}
int callPythonAdd(PyObject *module, int a, int b) {
//获取模块字典属性
PyObject *pDict = PyModule_GetDict(module);
if (pDict == nullptr) {
PyErr_Print();
std::cout << "PyModule_GetDict error" << std::endl;
return kError;
}
//直接获取模块中的函数
PyObject *addFunc = PyDict_GetItemString(pDict, "add");
if (addFunc == nullptr) {
std::cout << "PyDict_GetItemString 'add' not found" << std::endl;
return kError;
}
// 构造python 函数入参, 接收2
// see: https://docs.python.org/zh-cn/3.7/c-api/arg.html?highlight=pyarg_parse#c.PyArg_Parse
PyObject *pArg = Py_BuildValue("(i,i)", a, b);
//调用函数,并得到python类型的返回值
PyObject *result = PyEval_CallObject(addFunc, pArg);
int ret = 0;
//将python类型的返回值转换为c/c++类型
PyArg_Parse(result, "i", &ret);
return ret;
}
int callPythonGetName(PyObject *module, std::string firstName, std::string &outName) {
//获取模块字典属性
PyObject *pDict = PyModule_GetDict(module);
if (pDict == nullptr) {
PyErr_Print();
std::cout << "PyModule_GetDict error" << std::endl;
return kError;
}
//直接获取模块中的函数
PyObject *addFunc = PyDict_GetItemString(pDict, "get_name");
if (addFunc == nullptr) {
std::cout << "PyDict_GetItemString 'add' not found" << std::endl;
return kError;
}
// 构造python 函数入参, 接收2
// see: https://docs.python.org/zh-cn/3.7/c-api/arg.html?highlight=pyarg_parse#c.PyArg_Parse
PyObject *pArg = Py_BuildValue("(s)", firstName.c_str());
//调用函数,并得到python类型的返回值
PyObject *result = PyEval_CallObject(addFunc, pArg);
char *name = nullptr;
//将python类型的返回值转换为c/c++类型
PyArg_Parse(result, "s", &name);
char tempStr[256] = {};
int strLen = strlen(name);
if (strLen > 256) {
return kError;
}
strcpy(tempStr, name);
outName = tempStr;
return kSuccess;
}
通过C/C++ 封装好之后,即可使用了:
int main() {
pythonInit();
//int argc = 1;
//wchar_t *argv[1] = {L"ocrTest"};
//PySys_SetArgv(argc, argv);
//直接运行python代码
PyRun_SimpleString("print('----------hello Python form C/C++')");
PyObject *helloModule = pythonImportModule("../", "hello");
if (helloModule == nullptr) {
return -1;
}
// call python add function
int result = callPythonAdd(helloModule, 1, 3);
cout << "1 + 3 = " << result << endl;
// call python get_name function
std::string fullName;
callPythonGetName(helloModule, "summer", fullName);
cout << fullName << endl;
pythonCleanup();
}
执行后输出:
----------hello Python form C/C++
curr sys.path ['/usr/lib64/python36.zip', '/usr/lib64/python3.6', '/usr/lib64/python3.6/lib-dynload', '/home/tengqing/.local/lib/python3.6/site-packages', '/usr/local/lib64/python3.6/site-packages', '/usr/local/lib/python3.6/site-packages', '/usr/lib64/python3.6/site-packages', '/usr/lib/python3.6/site-packages', '../']
1 + 3 = 4
your name is summer alice
Process finished with exit code 0
依赖安装
环境是CentOS 7,gcc是4.8.5默认的,这里要注意是使用python2还是3。
$ sudo yum install libstdc++ # version `CXXABI_1.3.8' not found 问题
$ sudo yum install binutils # 升级ld版本
$ sudo yum install python3 # python3 --version
$ sudo yum install python3-devel # c/c++调用python需要python.so
C/C++调用Python需要借助 Python.h
和 Python.so
,即上面 python3-devel
执行后能在 /usr/include
和 /usr/lib64
下找到。
CMakeLists
所以,在CMakeLists.txt中,我们需要配置python3-devel包:
cmake_minimum_required(VERSION 3.19)
project(test)
set(CMAKE_CXX_STANDARD 14)
# 这里需要配置python3的头文件和python.so的库位置,在centos7中,通过如下方式查看
#[tengqing@localhost card]$ ls /usr/include/python
#python2.7/ python3.6m/
set(PYTHON3_VERSION 3.6m)
if (UNIX)
set(PYTHON3_INCLUDE /usr/include/python${PYTHON3_VERSION}/)
set(PYTHON3_LIBS /usr/bin/python3.6m-x86_64-config)
endif()
# 指定生成可执行程序
add_executable(test main.cpp)
# 头文件搜索目录
target_include_directories(${PROJECT_NAME}
PUBLIC
${PYTHON3_INCLUDE} # 这样外部就能找到头文件所在目录了
)
# 动态库搜索目录
target_link_directories(${PROJECT_NAME}
PUBLIC
${PYTHON3_LIBS})
# 链接动态库
target_link_libraries(${PROJECT_NAME}
PRIVATE
python${PYTHON3_VERSION})
多线程环境下调用Python3
GIL简介
来自:https://zhuanlan.zhihu.com/p/146874652
在使用python解释器时,要注意GIL(全局解释锁)的工作原理以及对性能的影响。GIL保证在任意时刻只有一个线程在解释器中运行。在多线程环境中,python解释器工作原理如下:
- 设置GIL
- 切换到一个线程去运行
- 运行:
a. 指定数量的字节码指令,或者
b. 线程主动让出控制(可以调用time.sleep(0)) - 把线程设置为睡眠状态
- 解锁GIL
- 再次重复以上所有步骤
对性能的影响: 假如有一段两个线程的python代码,运行在一个两核CPU上。由于GIL的存在,两个线程无法真正并行执行,CPU占用率总是低于50%。
GIL是一个历史遗留问题,导致CPython多线程不能利用多个CPU内核的计算能力。为了利用多核,通常使用多进程的方法,或是通过Python调用C代码,由C来实现多线程。
注意,当在C/C++创建的线程中调用Python时,GIL需要通过函数PyGILState_Ensure()和PyGILState_Release()手动获取、释放。 C++
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
/* Perform Python actions here. */
result = CallSomeFunction();
/* evaluate result or handle exception */
/* Release the thread. No Python API allowed beyond this point. */
PyGILState_Release(gstate);
C++封装GIL和正确的使用方式
封装GIL
有点类似mutex互斥锁,为了方便使用,我们封装成自动释放的锁,如下:
// https://docs.python.org/zh-cn/3/c-api/init.html?highlight=pygilstate_ensure
class PyThreadStateLock {
public:
PyThreadStateLock() {
cout << "----------------PyGILState_Ensure start" << endl;
state = PyGILState_Ensure();
cout << "----------------PyGILState_Ensure end" << endl;
}
~PyThreadStateLock() {
cout << "----------------PyGILState_Release start" << endl;
PyGILState_Release(state);
cout << "----------------PyGILState_Release end" << endl;
}
private:
PyGILState_STATE state;
};
初始化python3修改
在主线程中初始化python后,需要调用 PyEval_SaveThread()
释放全局解释器。
static PyThreadState *g_mainThreadState = nullptr;
int ocr_card_init(){
//初始化python
Py_Initialize();
// 初始化线程支持
PyEval_InitThreads();
// release the GIL, store thread state, set the current thread state to NULL
g_mainThreadState = PyEval_SaveThread();
}
每次调用python申请GIL
每次调用python函数之前,获取一下全局解释器即可。
int ocr_card_detect(const char *image_path, char *out_buffer, int *buffer_len) {
PyThreadStateLock autoLock;
// call python function
// ...
return kSuccess;
}
退出前释放
程序结束前释放,别忘记了恢复线程状态,获取全局解释器。
void ocr_card_cleanup() {
if (g_mainThreadState != nullptr) {
//re-acquire the GIL (re-initialize the current thread state)
PyEval_RestoreThread(g_mainThreadState);
Py_Finalize();
}
}
遇到问题
version `CXXABI_1.3.8’ not found 问题
升级一下libstdc++库的版本即可。
sudo yum install libstdc++ # version `CXXABI_1.3.8' not found 问题
PyImport_ImportModule返回NULL
原因一:找不到*.py脚本,或者没有引入脚本所在路径
找不到python脚本,或者目录不正确,引入一下即可:
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
原因二:没有安装依赖
执行之前,需要安装一下相关依赖,比如:
$ pip3 install -r requirements.txt
原因三:python脚本有异常
没有正确安装,或者依赖出错,我们可以通过堆栈查看具体的错误,然后解决。
//引入模块, hello.py
PyObject *module = PyImport_ImportModule(name);
if (module == nullptr) {
// print stack, 看看具体是什么错误
PyErr_Print();
cout << "PyImport_ImportModule 'hello.py' not found" << endl;
return nullptr;
}