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;
}