【Python】(附代碼)入門 | 如何使用C/C++調(diào)用Python腳本
編者薦語
在進(jìn)行 C/C++ 進(jìn)行開發(fā)的時候,很多非核心的功能,可以使用 Python 進(jìn)行實現(xiàn),因此學(xué)習(xí)在 C/C++ 程序中如何調(diào)用 Python 程序也是非常有必要的。實現(xiàn)的方法有許多種,今天給大家介紹一種小編認(rèn)為的最方便的方法。
編者薦語
在進(jìn)行 C/C++ 進(jìn)行開發(fā)的時候,很多非核心的功能,可以使用 Python 進(jìn)行實現(xiàn),因此學(xué)習(xí)在 C/C++ 程序中如何調(diào)用 Python 程序也是非常有必要的。實現(xiàn)的方法有許多種,今天給大家介紹一種小編認(rèn)為的最方便的方法。
文章將會介紹一種通過嵌入 Python
來豐富你的 C/C++ 應(yīng)用程序的方法(Python/C API),這種方式會使您的應(yīng)用程序能夠在不改變程序原有功能的基礎(chǔ)上,使用 Python 編程語言而不是 C/C++ 語言來實現(xiàn)應(yīng)用程序的某些功能。
這種方式可以用于多種目的,主要目的是允許我們通過用 Python
編寫一些腳本來根據(jù)需要定制應(yīng)用程序(某些功能可以更容易地用 Python 編寫)。當(dāng)你嵌入
Python 時,主程序一般情況下與 Python 實現(xiàn)無關(guān),應(yīng)用程序的某些部分會在需要的時候調(diào)用 Python 解釋器,運(yùn)行一些我們編寫的 Python 代碼。所以,如果你需要嵌入 Python,那么你首先需要一個 C/C++ 主程序。
基本使用方法
Python 提供了一套 C API庫,使得開發(fā)者能很方便地從C/ C++ 程序中調(diào)用 Python 模塊,C++ 用戶應(yīng)該注意,盡管 API 是完全使用 C 來定義的,但頭文件已將入口點聲明為 extern "C",因此 API 在 C++ 中使用此 API 不必再做任何特殊處理。

具體的文檔參考官方指南:
https://docs.python.org/3.9/extending/embedding.html
如果需要使用這個套API,我們需要引入一個頭文件和一個庫文件,以Cmake構(gòu)建為例,我們需要在 CMakeLists.txt 中加入:
find_package (Python COMPONENTS Interpreter Development REQUIRED)target_include_directories(${PROJECT_NAME} PRIVATE ${Python_INCLUDE_DIRS})target_link_libraries(${PROJECT_NAME} PRIVATE ${Python_LIBRARIES})
Windows環(huán)境下應(yīng)該是需要保證如下兩個環(huán)境變量的存在(暫未測試)。

然后我們就可以在 main.cpp 中引入相應(yīng)的頭文件了:
#include <Python.h>之后,主程序要做的一件事就是是初始化 Python 解釋器:
Py_Initialize();當(dāng)然,既然需要初始化解釋器,作為可以在 C 語言中編寫的代碼,我們一般還需要對它進(jìn)行釋放(在不需要的時候):
Py_Finalize();現(xiàn)在我們變可以將需要運(yùn)行的 Python 語句以字符串的形式傳遞給 Python 解釋器:
PyRun_SimpleString("print("Hello world!")");舉個簡單的栗子
此方法沒辦法進(jìn)行數(shù)值的傳遞(暫不考慮轉(zhuǎn)換為字符串傳遞,畢竟麻煩的同時接收數(shù)據(jù)也是一個大問題):
初始化Python解釋器;
以字符串的形式傳遞代碼;
釋放python解釋器。
代碼如下:
int main(int, char **){Py_Initialize(); // 初始化python解釋器PyRun_SimpleString("import matplotlib.pyplot as plt"); // 運(yùn)行python代碼PyRun_SimpleString("plt.plot([1,2,3,4], [12,3,23,231])");PyRun_SimpleString("plt.show()");Py_Finalize(); // 釋放python解釋器};
結(jié)果如下:
顯然,我們成功的在 C++ 程序中調(diào)用 Python 并繪制圖像并顯示了出來。
常用數(shù)據(jù)類型
在python中有一句話叫做“一切皆對象”,這句話可以結(jié)合源碼更好的進(jìn)行理解。
在python里,一切變量、函數(shù)、類等,在解釋器中執(zhí)行時,都會在在堆中新建一個對象,并將名字綁定在對象上。
在 C/C++ 中,所有的 Python 類型都被聲明為 PyObject ,為了能夠操作 Python 的數(shù)據(jù),Python提供了各種數(shù)據(jù)類型和 C 語言數(shù)據(jù)類型的轉(zhuǎn)換操作:
PyObject *object; // 創(chuàng)建python的object的指針Py_DECREF(object); // 銷毀object
1.數(shù)字與字符串處理
在 Python/C API 中提供了 Py_BuildValue() 函數(shù)對數(shù)字和字符串進(jìn)行轉(zhuǎn)換處理,使之變成Python中相應(yīng)的數(shù)據(jù)類型。
PyObject* Arg = Py_BuildValue("(i, i)", 1, 2); //i表示創(chuàng)建int型變量2.列表操作
在 Python/C API 中提供了 PyList_New() 函數(shù)用以創(chuàng)建一個新的 Python 列表。PyList_New() 函數(shù)的返回值為所創(chuàng)建的列表。
3.元組操作
在 Python/C API 中提供了 PyTuple_New() 函數(shù),用以創(chuàng)建一個新的 Python 元組。PyTuple_New() 函數(shù)返回所創(chuàng)建的元組。
4.字典操作
在 Python/C API 中提供了 PyDict_New() 函數(shù)用以創(chuàng)建一個新的字典。PyDict_New() 函數(shù)返回所創(chuàng)建的字典。
5. cv::Mat 操作
cv::Mat應(yīng)該是我們會經(jīng)常使用的格式了,參考代碼如下:
PyObject *cvmat2py(cv::Mat &image){import_array();int row, col;col = image.cols; //列寬row = image.rows; //行高int channel = image.channels();int irow = row, icol = col * channel;npy_intp Dims[3] = {row, col, channel}; //圖像維度信息PyObject *pyArray = PyArray_SimpleNewFromData(channel, Dims, NPY_UBYTE, image.data);PyObject *ArgArray = PyTuple_New(1);0, pyArray);return ArgArray;}
更多操作請參考:https://docs.python.org/zh-cn/3/c-api/arg.html#building-values
舉個稍復(fù)雜一點的栗子
此方法可以進(jìn)行進(jìn)行數(shù)值的傳遞:
初始化Python解釋器;
從C++到Python轉(zhuǎn)換數(shù)據(jù);
用轉(zhuǎn)換后的數(shù)據(jù)做參數(shù)調(diào)用Python函數(shù);
把函數(shù)返回值轉(zhuǎn)換為C++數(shù)據(jù)結(jié)構(gòu);
釋放python解釋器。
C++代碼如下:
int main(int, char **){// 初始化python解釋器Py_Initialize();if (!Py_IsInitialized()){return -1;}// 添加編譯后文件的所在路徑PyRun_SimpleString("import sys");PyRun_SimpleString("sys.path.append('./')");// 要調(diào)用的python文件名auto pModule = PyImport_ImportModule("common"); // 記得復(fù)制到編譯后文件的路徑下,同時不用加后綴名“.py“if (!pModule){std::cout << "Can't find your xx.py file.";getchar();return -1;}//獲取模塊中的函數(shù)auto pFunc = PyObject_GetAttrString(pModule, "add"); //從字符串創(chuàng)建python函數(shù)對象// 參數(shù)類型轉(zhuǎn)換PyObject *pArg = Py_BuildValue("ii", 1, 2);//調(diào)用直接獲得的函數(shù),并傳遞參數(shù)auto *py_result = PyEval_CallObject(pFunc, pArg);int c_result;// 將python類型的返回值轉(zhuǎn)換為C類型PyArg_Parse(py_result, "i", &c_result);std::cout << "return: " << c_result << std::endl;};
Python代碼如下(命名為 common.py):
def add(a, b):print('a: ', a)print('b: ', b)return a + b
結(jié)果如下(代碼中設(shè)置的 python 文件搜索路徑為編譯后的文件路徑):

這樣比較簡單的兩個實現(xiàn)就完成了,更多操作請參考:https://docs.python.org/3.9/c-api/index.html#c-api-index
參考資料
https://blog.csdn.net/pipisorry/article/details/49532341
https://blog.csdn.net/qq_45401419/article/details/123562089
https://zhuanlan.zhihu.com/p/146874652
—THE END—
往期精彩回顧
適合初學(xué)者入門人工智能的路線及資料下載 (圖文+視頻)機(jī)器學(xué)習(xí)入門系列下載 機(jī)器學(xué)習(xí)及深度學(xué)習(xí)筆記等資料打印 《統(tǒng)計學(xué)習(xí)方法》的代碼復(fù)現(xiàn)專輯 機(jī)器學(xué)習(xí)交流qq群955171419,加入微信群請掃碼
