天天看點

c調用python腳本如何擷取結果_C/C++ 調用Python

由于平時需要使用python做一些任務腳本,腳本裡面主要包含了任務流程所需要指令執行和傳回序列,而這些指令最終是需要到c/c++編寫的程式代碼中執行的,是以需要在c/c++中調用和解析python腳本。下面具體介紹了調用流程

C/C++ 調用Python

目錄前言

官方文檔

環境搭建

編譯連結

Demo

解釋器

初始化

GIL

Object

一切皆對象

從Python代碼中擷取Object

C/C++與Object轉換

函數調用

引用計數

參考資料

前言

最近項目中遇到需要用C++調用python代碼的情況,在網上搜尋後發現中文資料比較少。是以借此機會一邊學習一邊整理成文檔,友善後續查閱。

官方文檔

環境搭建

編譯連結

使用python提供的C/C++接口,需要包含python安裝目錄下的頭檔案Python.h 編譯、連結時需要指定頭檔案、python庫的位址,不過不需要我們自己操心,python提供了一個腳本,可以自動推薦編譯、連結參數: Bash python-config –cflags python-config –ldflags

Demo

動過運作一個簡單的demo,可以驗證鍊路是否打通。 C++

#include

int main(int argc, char *argv[]) {

Py_Initialize();

PyRun_SimpleString("print('hello world')\n");

Py_Finalize();

return 0;

}

// g++ main.cpp -I$PYTHON_PATH/include/python2.7 -lpython2.7

// 輸出 hello world

解釋器

初始化

在調用python API時,首先需要初始化全局解釋器,并且在使用完後銷毀。在我們的業務場景下,需要解釋器常駐記憶體,是以Py_Initialize在系統初始化時調用,Py_Finalize在析構函數中調用。

C++

void Py_Initialize(void)

int Py_IsInitialized(void)

void Py_Finalize()

初始化Python後,可以通過int PyRun_SimpleString(const char *command)函數令解釋器執行任意python代碼。這種叫做高層接口。高層接口雖然友善,但很難與C/C++交換資料。是以對于複雜需求,應該使用低層接口。雖然需要多寫很多C代碼,但可以靈活的實作很多複雜功能。

從操作步驟上看,C++調用Python低層接口可以分為幾個階段初始化Python解釋器

從C++到Python轉換資料

用轉換後的資料做參數調用Python函數

把函數傳回值轉換為C++資料結構

GIL

在使用python解釋器時,要注意GIL(全局解釋鎖)的工作原理以及對性能的影響。GIL保證在任意時刻隻有一個線程在解釋器中運作。在多線程環境中,python解釋器工作原理如下: Plain Text

1. 設定GIL

2. 切換到一個線程去運作

3. 運作:

a. 指定數量的位元組碼指令,或者

b. 線程主動讓出控制(可以調用time.sleep(0))

4. 把線程設定為睡眠狀态

5. 解鎖GIL

6. 再次重複以上所有步驟

對性能的影響: 假如有一段兩個線程的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();

result = CallSomeFunction();

PyGILState_Release(gstate);

Object

###一切皆對象 在python中有一句話叫做“一切皆對象”,這句話可以結合源碼更好的進行了解。在python裡,一切變量、函數、類等,在解釋器中執行時,都會在在堆中建立一個對象,并将名字綁定在對象上。 Python

i = 1 -----建立一個PyIntObject對象,然後綁定到i上

s = "abcde" -----建立一個PyStringObject對象,綁定到s上

def foo(): pass -----建立一個PyFunctionObject對象, 綁定到foo上

class C(object): pass -----建立一個類對象,綁定到C上

instance = C() -----建立一個執行個體對象,綁定到instance上

l = [1,2] -----建立一個PyListObject對象,綁定到l上

t = (1,2) -----建立一個PyTupleObject對象,綁定到t上

在Python/C API中,使用指向堆中對象的指針PyObject*對這些對象進行進行管理。是以,python中的大多數語句,都可以通過對PyObject指針調用各種函數來實作。

從Python代碼中擷取Object

如上一節所述,既然一切皆對象,那我們就可以在C/C++中擷取到python代碼中的對象。

C++

// Python内建函數import,導入一個Python子產品。

PyObject* PyImport_ImportModule(char *name)

// Python語句o.attr_name,傳回對象o中檢索attr_name屬性或方法。

PyObject* PyObject_GetAttrString(PyObject *o, char*attr_name)

C/C++與Object轉換

可以通過調用Py_BuildValue,通過傳遞格式字元串和變長參數,将C/C++變量構造為變量或元組。 C

PyObject* Py_BuildValue(const char *format, ...)

// 更多參數查閱參考官方文檔

// s 将C字元串轉換為Python字元串對象。

// i 将C int轉換為Python整數對象。

// d 将C double轉換為Python浮點數。

此外也可以直接調用下面一系列函數,顯式将C/C++變量轉換為python變量。 C++

// 基本變量

PyObject* PyLong_FromLong(long v)

PyObject* PyBool_FromLong(long v)

PyObject* PyFloat_FromDouble(double v)

// python元組

PyObject* PyTuple_New(Py_ssize_t len)

int PyTuple_SetItem(PyObject *p, Py_ssize_t pos, PyObject *o)

使用例 C++

// 通過set item的方式構造tuple

PyObject* args = PyTuple_New(3);

PyObject* arg1 = Py_BuildValue("i", 100); // 整數參數

PyObject* arg2 = Py_BuildValue("f", 3.14); // 浮點數參數

PyObject* arg3 = Py_BuildValue("s", "hello"); // 字元串參數

PyTuple_SetItem(args, 0, arg1);

PyTuple_SetItem(args, 1, arg2);

PyTuple_SetItem(args, 2, arg3);

// 通過buildvalue直接構造tuple

PyObject* args = Py_BuildValue("(ifs)", 100, 3.14, "hello");

PyObject* args = Py_BuildValue("()"); // 無參函數

PyObject也可以轉換為C++變量 C++

// 使用一系列庫函數轉換基本變量

long PyLong_AsLong(PyObject *obj)

long PyInt_AsLong(PyObject *obj)

double PyFloat_AsDouble(PyObject *obj)

string PyString_AsString(PyObject *obj)

// 元組

Py_ssize_t PyTuple_Size(PyObject *p)

PyObject* PyTuple_GetItem(PyObject *p, Py_ssize_t pos)

函數調用

由于Python中一切皆對象,是以函數、方法等調用都可以通過PyObject_Call系列函數完成。 C++

// callable(*args, **kwargs)

PyObject* PyObject_Call(PyObject *callable, PyObject *args, PyObject *kwargs)

PyObject* PyObject_CallObject(PyObject *callable, PyObject *args)

// callable(arg1, arg2, ...)

PyObject* PyObject_CallFunction(PyObject *callable, const char *format, ...)

PyObject* PyObject_CallFunctionObjArgs(PyObject *callable, ..., NULL)

// obj.name(arg1, arg2, ...)

PyObject* PyObject_CallMethod(PyObject *obj, const char *name, const char *format, ...)

PyObject* PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ..., NULL)

可以發現,函數參數通常有兩種類型,一種是直接傳遞tuple、map兩類PyObject,另一種是通過格式字元串與變長參數,直接将C/C++變量解析成參數。

還用一種結合兩種參數的方法,通過PyArg_ParseTuple()、PyArg_ParseTupleAndKeywords()和PyArg_Parse()三個函數,可以用格式字元串将C/C++變量構造為Python元組、字典或變量,以便後續函數調用。

引用計數

記憶體管理

Python使用引用計數與垃圾回收來管理記憶體。對于每個對象,可以了解為有一個對象實體以及若幹對該實體的引用(指向對象的指針)。引用計數通過記錄對象被引用的次數來管理對象,增加對對象的引用會使引用計數加一,減少對對象的引用使引用計數減一,當一個對象引用計數為0時釋放該對象占用的記憶體。由于引用計數無法處理循環引用的情況,還會有垃圾回收機制來處理循環引用的對象。可以認為Python解釋器會周期的調用垃圾回收。 所有的python對象,PyObject都有對象類型和引用計數。對象類型确定它是什麼樣的對象 (如,整數、清單或使用者定義的函數)。對于每個已知類型,都有一個宏來檢查對象是否屬于該類型。例如,如果指向的對象是 Python 清單, 則 PyList_Check (a) 為 true。

Python/C API 中的引用計數

API中使用兩個宏Py_INCREF(x) 和 Py_DECREF(x)來處理引用計數的增加和減少。當計數達到零時,Py_DECREF() 會釋放對象。如果是清單等複合對象類型,Py_DECREF還會遞減對象中包含的其他對象的引用計數。如果忘記減少引用計數将會造成記憶體洩漏。 有一個概念叫做引用的所有權,擁有所有權代表該引用不使用時需要調用Py_DECREF。所有權可以被傳遞,獲得所有權的新引用也需要在不使用時調用Py_DECREF。 此外還有一個概念叫做“借用(borrow)”的引用,類似C++中std::weak_ptr。借用的引用不能長期持有對象,沒有所有權。對象原持有者釋放對象後,借用引用再通路被釋放的記憶體會有風險。借用的引用可以通過調用Py_INCREF()改為真正持有引用。 所有權傳遞常發生在函數調用:作為傳回值:當函數傳回一個引用給調用方時,分為兩種情況:調用方獲得引用所有權或借用了引用而沒有獲得所有權。

以 PyObject_、PyNumber_、PySequence_ 或 PyMapping_ 開頭的函數總是傳遞所有權,新增它們傳回的對象的引用計數,調用方要在使用完後調用Py_DECREF ()。

而PyTuple_GetItem ()、PyList_GetItem ()、PyDict_GetItem () 和 PyDict_GetItemString () 都傳回從元組、清單或字典中借用的引用。

作為參數:當引用作為參數傳遞給函數時,有兩種情況:函數竊取引用或沒有竊取。竊取引用代表調用方不需要再處理該引用。

很少有函數竊取引用,常用的是PyList_SetItem()和PyTuple_SetItem()。

而PyObject_SetItem()、PyDict_SetItem()不竊取引用。

用一個例子來說明所有權傳遞問題,分别用PyList_GetItem()、 PySequence_GetItem()實作對list中所有數字求和: C++

long

sum_list(PyObject *list)

{

Py_ssize_t i, n;

long total = 0, value;

PyObject *item;

n = PyList_Size(list);

if (n < 0)

return -1;

for (i = 0; i < n; i++) {

item = PyList_GetItem(list, i);

if (!PyLong_Check(item)) continue;

value = PyLong_AsLong(item);

if (value == -1 && PyErr_Occurred())

return -1;

total += value;

}

return total;

}

C++

long

sum_sequence(PyObject *sequence)

{

Py_ssize_t i, n;

long total = 0, value;

PyObject *item;

n = PySequence_Length(sequence);

if (n < 0)

return -1;

for (i = 0; i < n; i++) {

item = PySequence_GetItem(sequence, i);

if (item == NULL)

return -1;

if (PyLong_Check(item)) {

value = PyLong_AsLong(item);

Py_DECREF(item);

if (value == -1 && PyErr_Occurred())

return -1;

total += value;

}

else {

Py_DECREF(item);

}

}

return total;

}

參考資料嵌套python解釋器

淺析 C++ 調用 Python 子產品

C/C++與python互相調用

python 一切皆對象

深入了解 GIL:如何寫出高性能及線程安全的 Python 代碼

Python進階特性:全局解釋器鎖GIL基本概念

python引用計數和gc垃圾回收