C++調用Python函數(CodeBlocks平臺實現)

因需,博主需要編寫C++程序,在其中調用Python編寫的相關函數,故此,整理了C++調用Python函數的相關方法

對於Python的版本,提及了常用的 Python2.7 與 Python3.5 版本,其餘常用的 3.x 版本的使用方法與 3.5 版本的類似,區別將會在教程中點明

對於調用的Python函數,可以是無參的或有參的,可以是有返回值的或是無返回值的

CodeBlocks 環境配置

該部分借鑑了 C++程序調用Python的函數(簡單應用)及Ubuntu16.04下codeblocks的環境配置 一文,對其進行了一定的補充說明

該部分針對Ubuntu系統實現,對於其餘系統(包括Windows),差別僅僅是Python相關文件的所在位置不同,可手動搜索其存儲位置,再按本教程的配置方法實現

針對Python環境的配置,需要知道Python的相關文件位置,可通過以下命令查找Python的相關文件所在

whereis python

在這裏插入圖片描述
該部分的環境配置是基於某一工程實現,因此需要建立工程,再對其進行設置

在建立的工程名上鼠標右鍵點擊,在彈出的選項中選擇 Build options…

在這裏插入圖片描述
而後,在新彈出的窗口中選擇 Search directories --> Compiler --> Add --> ,步驟如圖

在這裏插入圖片描述
在文件系統中選擇 /usr/include/python3.5 文件夾,其中 python3.5 可以根據需要換成其他版本,但該文件必須是 include 類型文件,選擇確認後會有一個 Keep this as a relative path? 問題,選擇 ,而後在 Add directory 窗口點擊確定,結果如圖

在這裏插入圖片描述
同樣在 Search directories 中,選擇 Linker,同樣添加相關文件 /usr/lib/python3.5,與之前相同,文件版本可替換,但 CompilerLinker 選擇的版本必須一致

在這裏插入圖片描述
在這裏插入圖片描述
而後,在同一窗口中,選擇 Linker settings,在其中的 Link Libiaries 中添加文件 /usr/lib/python3.5/config-3.5m-x86_64-linux-gnu/libpython3.5.so ,注意點與之前一致,確認後結果如圖

在這裏插入圖片描述
在這裏插入圖片描述
最後,點擊 Project build options 窗口的 確定 按鈕,保存環境配置

調用Python函數

該部分教程僅使用了C++與Python自設的調用方法,並未使用第三方軟件實現,該方法在官網中亦有提及,但官網的描述細節部分不足,直接運行其提供的代碼會有報錯,僅供參考(官網網址:Python 3.5.9 documentation,其中可選擇其他版本的幫助文檔)

本教程提供的方法同時適用於Python3與Python2.7版本,版本不同而需要修改的一些細節會在方法中點明

程序文件列表

在CodeBlocks中建立了一個名爲 Python3 的工程,下圖是該工程中的文件列表,其中 main.cpp 爲編寫的C++程序, test.py 爲Python程序,其餘文件爲工程自動創建或運行後自動創建

在這裏插入圖片描述

Python程序

文件 test.py 中內容如下,僅包含四個函數,有無參數與有無返回值的區別

在這裏插入圖片描述

C++程序

1、頭文件部分

需要添加 #include “python3.5/Python.h” 頭文件,其中 python3.5 部分可以修改爲需要的版本

#include <iostream>
#include "python3.5/Python.h"
using namespace std;

2、Python參數類型

該部分爲C++程序中使用的Python數據

PyObject *pName, *pModule, *pFunc, *pArgs, *pValue;

3、Python調用初始化與資源釋放

Python資源的初始化與釋放是一對操作

Py_Initialize();  //初始化
……
Py_Finalize();  //釋放資源

4、Python模塊目錄切換

其中 string path 是根據博主自己的模塊目錄填寫

string chdir_cmd 將模塊目錄加入系統的操作是博主搜索了衆多博客,最終找到的可以成功運行的方法

//將Python工作路徑切換到待調用模塊所在目錄
string path = "/home/wjh/桌面/Python3";
string chdir_cmd = string("sys.path.insert(0,\"") + path + "\")";
const char* cstr_cmd = chdir_cmd.c_str();
PyRun_SimpleString("import sys");
PyRun_SimpleString(cstr_cmd);

5、調用模塊

其中 PyUnicode_DecodeFSDefault(“test”) 中的 test 是模塊名,即編寫Python程序時import之後的名稱,本教程中,博主編寫了一個Python程序直接調用,因此該程序的模塊名即爲其文件名

以下代碼中,因該教程涉及的模塊調用方法較爲簡單,註釋部分的代碼等同於以上三行代碼,具體的差別可自行查看官方幫助文檔,可在其中搜索相關函數查看具體描述

pName = PyUnicode_DecodeFSDefault("test");  //模塊名
pModule = PyImport_Import(pName);  //調用模塊
Py_DECREF(pName);
//pModule = PyImport_ImportModule("test");  //調用模塊

以上代碼適用於Python3版本,Python2.7版本需要將第一行代碼修改爲如下函數,其餘部分保持不變(包括註釋的代碼,其效用仍舊等同於修改後的三行代碼)

pName = PyString_FromString("test");  //模塊名

6、調用函數

其中 pModule 爲Python模塊,“hello” 爲需要調用的函數名

pFunc = PyObject_GetAttrString(pModule, "hello");  //調用函數

7、函數參數設置

PyTuple_New 函數用於設置需要調用的Python函數的參數個數
PyLong_FromLong 函數設置參數值,設置的參數類型爲long,若需要設置其他類型的參數,可查閱官方文檔獲取相關函數
PyTuple_SetItem 函數用於將設置的參數值放入參數元組中,其中 pArgs 爲參數元組,pValue 爲參數值,中間的數字代表該參數值爲第幾個參數(從0開始)

pArgs = PyTuple_New(2);  //參數個數
pValue = PyLong_FromLong(1);  //參數值來源
PyTuple_SetItem(pArgs, 0, pValue);  //參數設置
pValue = PyLong_FromLong(2);  //參數值來源
PyTuple_SetItem(pArgs, 1, pValue);  //參數設置

8、函數執行

函數執行涉及兩個函數 PyEval_CallObjectPyObject_CallObject ,其中 PyEval_CallObject 用於無參數無返回值的Python函數,其餘情況使用 PyObject_CallObject 函數

其中 pFunc 爲需要調用的Python函數,NULL 表示無參數,pArgs 表示函數參數,pValue 爲函數返回值

PyEval_CallObject(pFunc, NULL);  //調用無參數無返回值的Python函數
pValue = PyObject_CallObject(pFunc, NULL);  //調用無參數有返回值的Python函數
PyObject_CallObject(pFunc, pArgs);  //調用有參數無返回值的Python函數
pValue = PyObject_CallObject(pFunc, pArgs);  //調用有參數有返回值的Python函數

9、返回值獲取

該部分涉及的函數較多,且Python3與Python2.7版本的函數存有差別,具體的使用需要查閱官方文檔提供的函數介紹

以下命令以 long 類型爲例,適用於Python的各個版本,其中 pValue 爲函數執行的返回值

//PyLong_AsLong(pValue)
cout << PyLong_AsLong(pValue) << endl << endl;

10、完整代碼

該部分代碼包含了以上所有內容,調用了四類不同的Python函數,但需要注意的是,該代碼適用於Python3.5,其餘版本需要對其進行一點修改

#include <iostream>
#include "python3.5/Python.h"

using namespace std;

int main() {

    PyObject *pName, *pModule, *pFunc, *pArgs, *pValue;

    Py_Initialize();  //初始化

    //將Python工作路徑切換到待調用模塊所在目錄
    string path = "/home/wjh/桌面/Python3";
    string chdir_cmd = string("sys.path.insert(0,\"") + path + "\")";
    const char* cstr_cmd = chdir_cmd.c_str();
    PyRun_SimpleString("import sys");
    PyRun_SimpleString(cstr_cmd);

    pName = PyUnicode_DecodeFSDefault("test");  //模塊名
    pModule = PyImport_Import(pName);  //調用模塊
    Py_DECREF(pName);
    //pModule = PyImport_ImportModule("test");  //調用模塊

    //1 無參數無返回
    cout << "1 無參數無返回" << endl;
    pFunc = PyObject_GetAttrString(pModule, "hello");  //調用函數
    PyEval_CallObject(pFunc, NULL);  //調用無參數無返回值的Python函數
    cout << endl;

    //2 無參數有返回
    cout << "2 無參數有返回" << endl;
    pFunc = PyObject_GetAttrString(pModule, "add");  //調用函數
    pValue = PyObject_CallObject(pFunc, NULL);
    cout << PyLong_AsLong(pValue) << endl << endl;

    //3 有參數無返回
    cout << "3 有參數無返回" << endl;
    pFunc = PyObject_GetAttrString(pModule, "sub");  //調用函數
    pArgs = PyTuple_New(2);  //參數個數
    pValue = PyLong_FromLong(1);  //參數值來源
    PyTuple_SetItem(pArgs, 0, pValue);  //參數設置
    pValue = PyLong_FromLong(2);  //參數值來源
    PyTuple_SetItem(pArgs, 1, pValue);  //參數設置
    PyObject_CallObject(pFunc, pArgs);
    cout << endl;

    //4 有參數有返回
    cout << "4 有參數有返回" << endl;
    pFunc = PyObject_GetAttrString(pModule, "Add");  //調用函數
    pArgs = PyTuple_New(2);  //參數個數
    pValue = PyLong_FromLong(1);  //參數值來源
    PyTuple_SetItem(pArgs, 0, pValue);  //參數設置
    pValue = PyLong_FromLong(2);  //參數值來源
    PyTuple_SetItem(pArgs, 1, pValue);  //參數設置
    pValue = PyObject_CallObject(pFunc, pArgs);
    cout << PyLong_AsLong(pValue) << endl << endl;

    Py_Finalize();

    return 0;
}

執行結果
在這裏插入圖片描述
11、備註說明

C++調用Python函數提供了許多判斷某一步驟是否出錯的函數,類似Java中try與catch涉及的函數操作,具體部分仍舊可參考官方文檔,本教程僅提供常用的一些判斷操作

檢查初始化是否成功

if (!Py_IsInitialized()) {
	return 1;
}

判斷模塊是否調用成功

if (pModule != NULL) {
    //調用成功
} else {
    PyErr_Print();  //錯誤打印到命令行
    return 1;
}

判斷函數是否調用成功,其中使用了 Py_XDECREF() 解除Python對象的引用,以便回收

if (pFunc && PyCallable_Check(pFunc)) {
	//調用成功
} else {
	if (PyErr_Occurred())
    	PyErr_Print();  //錯誤打印到命令行
}
Py_XDECREF(pFunc);
Py_DECREF(pModule);

判斷函數是否獲得返回值

if (pValue != NULL) {
	printf("Result of call: %ld\n", PyLong_AsLong(pValue));
	Py_DECREF(pValue);
} else {
	Py_DECREF(pFunc);
    Py_DECREF(pModule);
    PyErr_Print();
    return 1;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章