![](/img/trans.png)
[英]How to wrap a C++ object using pure Python Extension API (python3)?
[英]Wrap Complex C++ Class using the Python Extension API
我對創建可以在 Python 中使用的 C++ 類非常陌生。 我瀏覽了互聯網上的很多帖子。 無論是在 StackOverflow、gist、github 上,...我也閱讀了文檔,但我不確定如何解決我的問題。
基本上,這個想法是這樣做的: http : //www.speedupcode.com/c-class-in-python3/因為我想避免創建自己的python PyCapsule_New
的負擔,我認為使用PyCapsule_New
和PyCapsule_GetPointer
就像上面的例子可能是一種解決方法,但也許我在誤導,我仍然需要創建復雜的數據類型。
這是我希望能夠從 python 調用的類的標題:
template<typename T>
class Graph {
public:
Graph(const vector3D<T>& image, const std::string& similarity, size_t d) : img(image) {...}
component<T> method1(const int k, const bool post_processing=true);
private:
caller_map<T> cmap;
vector3D<T> img; // input image with 3 channels
caller<T> sim; // similarity function
size_t h; // height of the image
size_t w; // width of the image
size_t n_vertices; // number of pixels in the input image
size_t conn; // radius for the number of connected pixels
vector1D<edge<T>> edges; // graph = vector of edges
void create_graph(size_t d);
tuple2 find(vector2D<subset>& subsets, tuple2 i);
void unite(vector2D<subset>& subsets, tuple2 x, tuple2 y);
};
所以正如你所看到的,我的班級包含復雜的結構。 vector1D
只是std::vector
但邊緣是由定義的結構
template<typename T>
struct edge {
tuple2 src;
tuple2 dst;
T weight;
};
有些方法使用其他復雜的結構。
無論如何,我已經創建了自己的 Python 綁定。 這里我只放了相關的功能。 我創建了我的constructor
如下:
static PyObject *construct(PyObject *self, PyObject *args, PyObject *kwargs) {
// Arguments passed from Python
PyArrayObject* arr = nullptr;
// Default if arguments not given
const char* sim = "2000"; // similarity function used
const size_t conn = 1; // Number of neighbor pixels to consider
char *keywords[] = {
"image",
"similarity",
"d",
nullptr
};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|sI:vGraph", keywords, PyArray_Converter, &arr, &sim, &conn)) {
// Will need to DECRF(arr) somewhere?
return nullptr;
}
set<string> sim_strings = {"1976", "1994", "2000"};
if (sim_strings.find(sim) == sim_strings.end()) {
PyErr_SetString(PyExc_ValueError, "This similarity function does not exist");
Py_RETURN_NONE;
}
// Parse the 3D numpy array to vector3D
vector3D<float> img = parse_PyArrayFloat<float>(arr);
// call the Constructor
Graph<float>* graph = new Graph<float>(img, sim, conn);
// Create Python capsule with a pointer to the `Graph` object
PyObject* graphCapsule = PyCapsule_New((void * ) graph, "graphptr", vgraph_destructor);
// int success = PyCapsule_SetPointer(graphCapsule, (void *)graph);
// Return the Python capsule with the pointer to `Graph` object
// return Py_BuildValue("O", graphCapsule);
return graphCapsule;
}
在調試我的代碼時,我可以看到我的構造函數返回了我的 graphCapsule 對象,並且它與nullptr
不同。
然后我創建我的method1
函數如下:
static PyObject *method1(PyObject *self, PyObject *args) {
// Capsule with the pointer to `Graph` object
PyObject* graphCapsule_;
// Default parameters of the method1 function
size_t k = 300;
bool post_processing = true;
if (!PyArg_ParseTuple(args, "O|Ip", &graphCapsule_, &k, &post_processing)) {
return nullptr;
}
// Get the pointer to `Graph` object
Graph<float>* graph = reinterpret_cast<Graph<float>* >(PyCapsule_GetPointer(graphCapsule_, "graphptr"));
// Call method1
component<float> ctov = graph->method1(k, post_processing);
// Convert component<float> to a Python dict (bad because we need to copy?)
PyObject* result = parse_component<float>(ctov);
return result;
}
當我編譯所有內容時,我將有一個vgraph.so
庫,我將使用以下命令從 Python 中調用它:
import vgraph
import numpy as np
import scipy.misc
class Vgraph():
def __init__(self, img, similarity, d):
self.graphCapsule = vgraph.construct(img, similarity, d)
def method1(self, k=150, post_processing=True):
vgraph.method1(self.graphCapsule, k, post_processing)
if __name__ == "__main__":
img = scipy.misc.imread("pic.jpg")
img = scipy.misc.imresize(img, (512, 512)) / 255
g = Vgraph(lab_img, "1976", d=1)
cc = g.method1(k=150, post_processing=False)
這個想法是我保存vgraph.construct
返回的PyObject pointer
。 然后我調用method1
傳遞PyObject pointer
int k = 150
和bool postprocessing
。
這就是為什么在*method1
的 C++ 實現中,我使用: !PyArg_ParseTuple(args, "O|Ip", &graphCapsule_, &k, &post_processing)
來解析這 3 個對象。
問題是,即使我在調試時,我恢復了k=150
和post_processing=False
,它們來自我從 Python 調用 C++ 的方式......我也得到了0X0
,也就是說變量graphCapsule_
的nullptr
...
所以顯然其余的代碼無法工作......
我認為PyObject *
是一個指向我的類型的曲線Graph<float> *
,因此,我期待ParseTuple恢復我PyObject *
指針我然后可以在使用PyCapsule_GetPointer
檢索我的對象。
我怎樣才能讓我的代碼工作? 我是否需要定義我自己的 PyObject 以便 ParseTuple 理解它? 有沒有更簡單的方法來做到這一點?
非常感謝!
注意:如果我打破了我的 python 代碼,我可以看到我的圖形g
包含一個PyObject
及其指向的地址和對象的名稱(這里是graphtr
)所以我希望我的代碼能夠工作......
注意2 :如果我需要創建自己的newtype
,我已經看到了這個stackoverflow帖子: How to wrap a C++ object using pure Python Extension API (python3)? 但是我覺得因為我的Class對象比較復雜,會不會比較難?
我回答我自己的問題。
我實際上在我的代碼中發現了這個缺陷。
這兩個函數PyCapsule_GetPointer
和PyCapsule_New
工作得很好。 正如我的問題中提到的,在我嘗試使用以下代碼解析膠囊后,問題就出現了:
size_t k = 300;
bool post_processing = true;
if (!PyArg_ParseTuple(args, "O|Ip", &graphCapsule_, &k, &post_processing)) {
return nullptr;
}
問題來自其他參數的解析。 實際上,k 是size_t
類型,所以我應該使用n
作為文檔中提到的,而不是使用I
表示 unsigned int:
n (int) [Py_ssize_t]
Convert a Python integer to a C Py_ssize_t.
此外, post_processing
是一個布爾值,因此,即使文檔提到可以使用p
解析布爾值:
p (bool) [int]
我應該用類型int
而不是類型bool
初始化布爾值,因為它在這個stackoverflow 帖子中提到
因此,工作代碼是:
size_t k = 300;
int post_processing = true;
if (!PyArg_ParseTuple(args, "O|np", &graphCapsule_, &k, &post_processing)) {
return nullptr;
}
我們也可以使用O!
通過傳遞&Pycapsule_Type
選項:
#include <pycapsule.h>
...
size_t k = 300;
int post_processing = true;
if (!PyArg_ParseTuple(args, "O!|np", &PyCapsule_Type, &graphCapsule_, &k, &post_processing)) {
return nullptr;
}
最后,正如我的問題中提到的,基於此stackoverflow post實現您自己的 Python 類型實際上很簡單。 我剛剛復制/粘貼並根據我的需要調整了代碼,它就像一個魅力,不再需要使用PyCaspule
!
其他有用的信息:
要調試您的代碼(我在 Linux 上使用了 vscode),您可以使用混合語言調試。 這個想法是將您的 C++ 代碼編譯為共享庫.so
。
編譯代碼后,您可以將其導入python:
import my_lib
其中my_lib
指的是您生成的my_lib.so
文件。
要生成.so
文件,您只需要執行: g++ my_python_to_cpp_wrapper.cpp --ggdb -o my_python_to_cpp_wrapper.so
但是,如果你這樣做,你可能會錯過包含 python 庫和東西......
幸運的是,python 提供了一種為編譯和鏈接查找推薦標志的方法:
你只需要執行(更改你的python版本或最終查看/usr/local/bin)
/usr/bin/python3.6m-config --cflags
對我來說,它返回:
-I/usr/include/python3.6m -I/usr/include/python3.6m -Wno-unused-result -Wsign-compare -g -fdebug-prefix-map=/build/python3.6-0aiVHW/python3.6-3.6.9=. -specs=/usr/share/dpkg/no-pie-compile.specs -fstack-protector -Wformat -Werror=format-security -DNDEBUG -g -fwrapv -O3 -Wall
同樣申請鏈接(更改您的python版本或最終查看/usr/local/bin)
/usr/bin/python3.6m-config --ldflags
給我:
-L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu -L/usr/lib -lpython3.6m -lpthread -ldl -lutil -lm -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions
然后,因為我們要創建一個共享庫.so
我們需要添加-shared
標志還有-fPIC
標志(否則它會抱怨)。 最后,因為我們想要調試我們的代碼,所以我們應該刪除任何-Ox
比如優化代碼的-O2
或-O3
標志,因為在調試過程中,你會得到<optimized out>
提示。 為避免這種情況,請從g++
選項中刪除任何優化標志。 例如,就我而言,我的cpp文件名為: vrgaph.cpp ,這是我編譯它的方式:
g++ vgraph.cpp -ggdb -o vgraph.so -I/usr/include/python3.6m -I/usr/include/python3.6m -Wno-unused-result -Wsign-compare -g -fdebug-prefix-map=/build/python3.6-0aiVHW/python3.6-3.6.9=. -specs=/usr/share/dpkg/no-pie-compile.specs -fstack-protector -Wformat -Werror=format-security -DNDEBUG -g -fwrapv -Wall -L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu -L/usr/lib -lpython3.6m -lpthread -ldl -lutil -lm -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions -shared -fPIC
你可以看到我全光照-O1
,而不是-O2
或-O3
。
一次,編譯你將有一個.so
文件,你可以在 python 中導入和使用。 對於我的示例,我將使用vgraph.so
並且在我的 python 代碼中,我可以執行以下操作:
import vgraph.so
# rest of the call that use you C++ backend code
然后您就可以輕松調試 C++。 互聯網上有一些帖子解釋了如何使用 vs code/gdb/...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.