如何在C++中調(diào)用Python
Python的安裝
為了使用Python.h這個(gè)擴(kuò)展項(xiàng),我們需要安裝一個(gè)python*-dev而不是python*,這兩者略有區(qū)別,下面的案例展示的是在Ubuntu20.04下安裝python3.9-dev的方法:
dechin@ubuntu2004:~/projects/gitlab/dechin/$ sudo apt install python3.9-dev 正在讀取軟件包列表... 完成 正在分析軟件包的依賴(lài)關(guān)系樹(shù) 正在讀取狀態(tài)信息... 完成 下列軟件包是自動(dòng)安裝的并且現(xiàn)在不需要了: chromium-codecs-ffmpeg-extra gstreamer1.0-vaapi libgstreamer-plugins-bad1.0-0 linux-headers-5.8.0-43-generic linux-hwe-5.8-headers-5.8.0-43 linux-image-5.8.0-43-generic linux-modules-5.8.0-43-generic linux-modules-extra-5.8.0-43-generic 使用'sudo apt autoremove'來(lái)卸載它(它們)。 將會(huì)同時(shí)安裝下列軟件: libexpat1-dev libpython3.9 libpython3.9-dev zlib1g-dev 下列【新】軟件包將被安裝: libexpat1-dev libpython3.9 libpython3.9-dev python3.9-dev zlib1g-dev 升級(jí)了 0 個(gè)軟件包,新安裝了 5 個(gè)軟件包,要卸載 0 個(gè)軟件包,有 30 個(gè)軟件包未被升級(jí)。 需要下載 6,613 kB 的歸檔。 解壓縮后會(huì)消耗 28.7 MB 的額外空間。 您希望繼續(xù)執(zhí)行嗎? [Y/n] Y 獲取:1 http://repo.huaweicloud.com/ubuntu focal/main amd64 libexpat1-dev amd64 2.2.9-1build1 [116 kB] 獲取:2 http://repo.huaweicloud.com/ubuntu focal-updates/universe amd64 libpython3.9 amd64 3.9.0-5~20.04 [1,710 kB] 獲取:3 http://repo.huaweicloud.com/ubuntu focal-updates/universe amd64 libpython3.9-dev amd64 3.9.0-5~20.04 [4,119 kB] 獲取:4 http://repo.huaweicloud.com/ubuntu focal-updates/main amd64 zlib1g-dev amd64 1:1.2.11.dfsg-2ubuntu1.2 [155 kB] 獲取:5 http://repo.huaweicloud.com/ubuntu focal-updates/universe amd64 python3.9-dev amd64 3.9.0-5~20.04 [512 kB] 已下載 6,613 kB,耗時(shí) 4秒 (1,594 kB/s) 正在選中未選擇的軟件包 libexpat1-dev:amd64。 (正在讀取數(shù)據(jù)庫(kù) ... 系統(tǒng)當(dāng)前共安裝有 269544 個(gè)文件和目錄。) 準(zhǔn)備解壓 .../libexpat1-dev_2.2.9-1build1_amd64.deb ... 正在解壓 libexpat1-dev:amd64 (2.2.9-1build1) ... 正在選中未選擇的軟件包 libpython3.9:amd64。 準(zhǔn)備解壓 .../libpython3.9_3.9.0-5~20.04_amd64.deb ... 正在解壓 libpython3.9:amd64 (3.9.0-5~20.04) ... 正在選中未選擇的軟件包 libpython3.9-dev:amd64。 準(zhǔn)備解壓 .../libpython3.9-dev_3.9.0-5~20.04_amd64.deb ... 正在解壓 libpython3.9-dev:amd64 (3.9.0-5~20.04) ... 正在選中未選擇的軟件包 zlib1g-dev:amd64。 準(zhǔn)備解壓 .../zlib1g-dev_1%3a1.2.11.dfsg-2ubuntu1.2_amd64.deb ... 正在解壓 zlib1g-dev:amd64 (1:1.2.11.dfsg-2ubuntu1.2) ... 正在選中未選擇的軟件包 python3.9-dev。 準(zhǔn)備解壓 .../python3.9-dev_3.9.0-5~20.04_amd64.deb ... 正在解壓 python3.9-dev (3.9.0-5~20.04) ... 正在設(shè)置 libpython3.9:amd64 (3.9.0-5~20.04) ... 正在設(shè)置 libexpat1-dev:amd64 (2.2.9-1build1) ... 正在設(shè)置 zlib1g-dev:amd64 (1:1.2.11.dfsg-2ubuntu1.2) ... 正在設(shè)置 libpython3.9-dev:amd64 (3.9.0-5~20.04) ... 正在設(shè)置 python3.9-dev (3.9.0-5~20.04) ... 正在處理用于 man-db (2.9.1-1) 的觸發(fā)器 ... 正在處理用于 libc-bin (2.31-0ubuntu9.2) 的觸發(fā)器 ...
安裝完成后,如果在當(dāng)前命令行下運(yùn)行python3.9,是可以看到一個(gè)python專(zhuān)屬的命令行界面的,可以通過(guò)exit()退出。但是我們這里側(cè)重的是跟C++的配合工作,因此我們更加關(guān)注lib和include目錄下是否有生成相關(guān)的目錄,可以執(zhí)行如下指令進(jìn)行查看:
dechin@ubuntu2004:~/projects/gitlab/dechin/$ ll /usr/lib/ | grep python drwxr-xr-x 26 root root20480 5月7 16:27 python2.7/ drwxr-xr-x3 root root 4096 2月 10 02:47 python3/ drwxr-xr-x 30 root root20480 5月7 16:30 python3.8/ drwxr-xr-x 31 root root12288 5月 20 16:31 python3.9/
這里我們看到有一個(gè)3.9的版本,也就是我們剛才安裝的版本,再看看include下的目錄:
dechin@ubuntu2004:~/projects/gitlab/dechin/$ ll /usr/include/ | grep python drwxr-xr-x 2 root root4096 5月7 16:31 python3.8/ drwxr-xr-x 4 root root4096 5月 20 16:31 python3.9/
這里我們就可以看到一些區(qū)別了,有一些版本的python不一定會(huì)有這兩個(gè)目錄,但是只有具備了這兩個(gè)目錄,才能夠被C++調(diào)用。
VS Code配置
這里我們使用的IDE是VS Code,但是上述提到的幾個(gè)路徑,在VS Code中默認(rèn)是不被包含的,因此在代碼編輯的過(guò)程中在include <Python.h>這一步就會(huì)報(bào)錯(cuò)了。這一章節(jié)的目的主要是解決IDE中的報(bào)錯(cuò)問(wèn)題,還不是最終運(yùn)行中出現(xiàn)的問(wèn)題,因?yàn)檫\(yùn)行時(shí)我是通過(guò)命令行執(zhí)行g(shù)++來(lái)運(yùn)行的,而不是直接用IDE來(lái)跑。首先在VS Code界面上按順序同時(shí)按?。篶trl+shift+P,在彈出的窗口中輸入C/C++ Edit Configurations(JSON)查找相關(guān)JSON配置文件,在列表中點(diǎn)擊后會(huì)自動(dòng)在VS Code中打開(kāi)這個(gè)配置文件:
{ "configurations": [ { "name": "Linux", "includePath": [ "${workspaceFolder}/**" ], "defines": [], "compilerPath": "/usr/bin/gcc", "cStandard": "gnu17", "cppStandard": "c++11", "intelliSenseMode": "linux-gcc-x64" } ], "version": 4 }
我們所需要做的工作就是,在這個(gè)includePath中把相關(guān)的路徑都加上,比如我這邊添加的路徑是以下3個(gè):
{ "configurations": [ { "name": "Linux", "includePath": [ "${workspaceFolder}/**", "/usr/include/python3.9/", "/usr/lib/python3.9/", "/usr/include/python3.9/cpython/" ], "defines": [], "compilerPath": "/usr/bin/gcc", "cStandard": "gnu17", "cppStandard": "c++11", "intelliSenseMode": "linux-gcc-x64" } ], "version": 4 }
添加后,include <Python.h>就不會(huì)顯示報(bào)錯(cuò)了。
Hello World測(cè)試
行業(yè)潛規(guī)則,我們先用C++來(lái)調(diào)用一個(gè)Python的打印函數(shù),輸出Hello World試試:
// cp.cpp #include <Python.h> int main(int argc, char *argv[]) { Py_Initialize(); PyRun_SimpleString("print('hello world')\n"); Py_Finalize(); return 0; }
這里需要注意的是一個(gè)運(yùn)行方式,我們是用g++來(lái)進(jìn)行編譯的,但是g++默認(rèn)是找不到我們剛才在IDE中所設(shè)定的幾個(gè)includePath的,因此需要我們手動(dòng)在編譯的時(shí)候加上幾個(gè)參數(shù)。這些參數(shù)其實(shí)也可以運(yùn)行python3.9-config去一個(gè)一個(gè)查看,這里我們直接推薦一種可以運(yùn)行成功的參數(shù),其中最重要的是-I和-l這兩個(gè)路徑一定要包含:
dechin@ubuntu2004:~/projects/gitlab/dechin/$ g++ -o cpy cp.cpp -lm -std=c++11 -I/usr/include/python3.9/ -lpython3.9 dechin@ubuntu2004:~/projects/gitlab/dechin/$ ll 總用量 4697388 drwxrwxr-x 2 dechin dechin 4096 5月 20 17:10 ./ drwxrwxr-x 8 dechin dechin 4096 5月 19 15:32 ../ -rw-rw-r-- 1 dechin dechin 152 5月 20 17:04 cp.cpp -rwxrwxr-x 1 dechin dechin16776 5月 20 17:10 cpy*
運(yùn)行完成后,就會(huì)在當(dāng)前目錄下生成一個(gè)剛才指定的名字cpy的一個(gè)可執(zhí)行文件,如果是windows系統(tǒng),則會(huì)生成一個(gè)cpy.exe的文件。讓我們執(zhí)行這個(gè)文件:
dechin@ubuntu2004:~/projects/gitlab/dechin/$ ./cpy hello world
成功打印Hello World,又離成功更近了一步。
調(diào)用Python函數(shù)string.split()
在C++中如果我們想分割一個(gè)字符串,雖然說(shuō)也是可以實(shí)現(xiàn)的,但是應(yīng)該沒(méi)有比Python中執(zhí)行一個(gè)string.split()更加方便快捷的方案了,因此我們測(cè)試一個(gè)用C++調(diào)用Python的split函數(shù)的功能。
第一次嘗試
一開(kāi)始我們是寫(xiě)了這樣一個(gè)簡(jiǎn)單的案例,用PyImport_ImportModule方法去調(diào)用pysplit這個(gè)python模塊:
// cp.cpp #include <Python.h> #include <iostream> using namespace std; int main(int argc, char *argv[]) { Py_Initialize(); if (!Py_IsInitialized()) { cout << "Initialize failed!" << endl; return 0; } PyObject* pModule = NULL; PyObject* pFunc; PyRun_SimpleString("import os"); PyRun_SimpleString("os.system('pwd')"); pModule = PyImport_ImportModule("pysplit"); if (pModule == NULL) { cout << "Module Not Found!" << endl; } // pFunc = PyObject_GetAttrString(pModule, "sp"); // PyObject* args = Py_BuildValue("s", "Test String Hello Every One !"); // PyObject* pRet = PyObject_CallObject(pFunc, args); string cList[10]; // PyArg_Parse(pRet, "[items]", &cList); cout << "res:" << cList << endl; Py_Finalize(); return 0; }
對(duì)應(yīng)的Python模塊的內(nèi)容為:
# pysplit.py def sp(string): return string.split()
這是一個(gè)非常簡(jiǎn)單的函數(shù),但是我們?cè)谡{(diào)用的時(shí)候就直接返回了一個(gè)錯(cuò)誤:
dechin@ubuntu2004:~/projects/gitlab/dechin/$ g++ -o cpy cp.cpp -lm -std=c++11 -I/usr/include/python3.9/ -lpython3.9 && ./cpy ['pysplit.py', 'cpy', 'cp.cpp'] Module Not Found! res:0x7ffc622ae900
這個(gè)錯(cuò)誤是說(shuō),找不到pysplit這個(gè)模塊。但是我們同時(shí)借助于PyRun_SimpleString調(diào)用了Python中的os庫(kù),執(zhí)行了一個(gè)查看路徑和當(dāng)前路徑下文件的功能,我們發(fā)現(xiàn)這個(gè)C++文件和需要引入的pysplit.py其實(shí)是在同一個(gè)路徑下的,這就很奇怪了沒(méi)有導(dǎo)入成功。
第二次嘗試
經(jīng)過(guò)一番的資料查詢(xún),最后發(fā)現(xiàn),即使是在相同的路徑下,也需要通過(guò)Python的sys將當(dāng)前目錄添加到系統(tǒng)路徑中,才能夠識(shí)別到這個(gè)模塊,同樣也是使用PyRun_SimpleString的函數(shù):
// cp.cpp #include <Python.h> #include <iostream> using namespace std; int main(int argc, char *argv[]) { Py_Initialize(); if (!Py_IsInitialized()) { cout << "Initialize failed!" << endl; return 0; } PyObject* pModule = NULL; PyObject* pFunc; PyRun_SimpleString("import sys"); PyRun_SimpleString("sys.path.append('./')"); pModule = PyImport_ImportModule("pysplit"); if (pModule == NULL) { cout << "Module Not Found!" << endl; } pFunc = PyObject_GetAttrString(pModule, "sp"); PyObject* args = Py_BuildValue("s", "Test String Hello Every One !"); PyObject* pRet = PyObject_CallObject(pFunc, args); string cList[10]; // PyArg_Parse(pRet, "[items]", &cList); cout << "res:" << cList << endl; Py_Finalize(); return 0; }
這個(gè)也可以理解,Python中的函數(shù)調(diào)用,輸入?yún)?shù)都被打包成了一個(gè)tuple格式,比如**args,而類(lèi)似**kwargs則是打包成一個(gè)字典格式,類(lèi)似的功能在這篇博客中有所介紹。
第三次嘗試
上面的問(wèn)題,在StackOverFlow上有一個(gè)類(lèi)似的情況,有一個(gè)回答解決了這個(gè)問(wèn)題,解決方案是,用PyObject_CallFunctionObjArgs來(lái)替代PyObject_CallObject去實(shí)現(xiàn)函數(shù)調(diào)用命令,相關(guān)代碼如下:
// cp.cpp #include <Python.h> #include <iostream> using namespace std; int main(int argc, char *argv[]) { Py_Initialize(); if (!Py_IsInitialized()) { cout << "Initialize failed!" << endl; return 0; } PyObject* pModule = NULL; PyObject* pFunc; PyRun_SimpleString("import sys"); PyRun_SimpleString("sys.path.append('./')"); pModule = PyImport_ImportModule("pysplit"); if (pModule == NULL) { cout << "Module Not Found!" << endl; } pFunc = PyObject_GetAttrString(pModule, "sp"); PyObject* args = Py_BuildValue("s", "Test String Hello Every One !"); PyObject* pRet = PyObject_CallFunctionObjArgs(pFunc, args, NULL); int size = PyList_Size(pRet); cout << "List size is: " << size << endl; for(int i=0;i<size;i++) { PyObject* cRet = PyList_GET_ITEM(pRet, i); char* s; PyArg_Parse(cRet, "s", &s); cout << "The " << i << "th term is: " << s << endl; } Py_Finalize(); return 0; }
最后,因?yàn)閺腜ython中獲取的是一個(gè)List格式的數(shù)據(jù),因此我們首先需要用PyList_GET_ITEM去逐項(xiàng)提取,然后用PyArg_Parse將提取出來(lái)的元素保存到一個(gè)C++的char字符串中,執(zhí)行結(jié)果如下:
dechin@ubuntu2004:~/projects/gitlab/dechin/$ g++ -o cpy cp.cpp -lm -std=c++11 -I/usr/include/python3.9/ -lpython3.9 && ./cpy List size is: 6 The 0th term is: Test The 1th term is: String The 2th term is: Hello The 3th term is: Every The 4th term is: One The 5th term is: !
Yes!終于成功了!
總結(jié)概要
本文介紹了一個(gè)在C++內(nèi)部調(diào)用Python中封裝的函數(shù)或者接口的方法,從環(huán)境配置到具體示例都有講解,并且在其中包含有不少的坑點(diǎn),需要一步一步去踩。不同的編程語(yǔ)言具有不同的優(yōu)勢(shì),Python輪子眾多而語(yǔ)法簡(jiǎn)單,上手容易,但是性能比較首先,C++的最明顯優(yōu)勢(shì)就是在于其性能的天然優(yōu)越性。但是我們不需要對(duì)哪一種編程語(yǔ)言有所偏倚,都有所掌握,并且能夠有所互通,利用好各自的優(yōu)勢(shì),才能夠發(fā)揮最大的價(jià)值。
以上就是如何在C++中調(diào)用Python的詳細(xì)內(nèi)容,更多關(guān)于C++ 調(diào)用Python的資料請(qǐng)關(guān)注本站其它相關(guān)文章!
版權(quán)聲明:本站文章來(lái)源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來(lái)源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來(lái)源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來(lái),僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。