gpt4 book ai didi

python - 使用 Python 扩展 API 包装复杂的 C++ 类

转载 作者:行者123 更新时间:2023-12-01 14:58:06 26 4
gpt4 key购买 nike

我对创建可以在 Python 中使用的 C++ 类非常陌生。我在网上浏览了很多帖子。无论是在 StackOverflow、gist、github 上,......我也阅读了文档,但我不确定如何解决我的问题。

基本上,这个想法是这样做的:http://www.speedupcode.com/c-class-in-python3/
因为我想避免创建自己的负担python newtype ,我认为使用 PyCapsule_NewPyCapsule_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但 edge 是由以下定义的结构

template<typename T>
struct edge {
tuple2 src;
tuple2 dst;
T weight;
};

有些方法使用其他复杂的结构。

无论如何,我已经创建了自己的 Python 绑定(bind)。这里我只放相关功能。我创建了我的 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)

这个想法是我保存 PyObject pointervgraph.construct 返回.然后我调用 method1通过 PyObject pointer int k = 150bool postprocessing .

这就是为什么在 *method1 的 C++ 实现中, 我用: !PyArg_ParseTuple(args, "O|Ip", &graphCapsule_, &k, &post_processing)解析这三个对象。

问题是,即使在调试时,我恢复了 k=150post_processing=False这来自我从 Python 调用 C++ 的方式......我也得到了 0X0 ,也就是说一个 nullptr在变量 graphCapsule_ ...

所以显然其余的代码不能工作......

我以为 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)?但是我想因为我的类的对象比较复杂,会比较难?

最佳答案

我回答我自己的问题。

我实际上发现了我的代码中的缺陷。

两种功能PyCapsule_GetPointerPyCapsule_New工作得很好。正如我的问题中提到的,在我尝试使用以下代码解析胶囊之后,问题就出现了:

size_t k = 300;
bool post_processing = true;

if (!PyArg_ParseTuple(args, "O|Ip", &graphCapsule_, &k, &post_processing)) {
return nullptr;
}


问题来自对其他参数的解析。
实际上,k 是 size_t输入 so 而不是使用 I对于 unsigned int,我应该使用 n作为 documentation提到:
n (int) [Py_ssize_t]
Convert a Python integer to a C Py_ssize_t.

此外, post_processing是一个 bool 值,即使 documentation提到可以用 p 解析 bool 值:
p (bool) [int]

我应该使用类型 int 初始化 bool 值而不是类型 bool正如本文中提到的 stackoverflow post

因此,工作代码是:

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/...

关于python - 使用 Python 扩展 API 包装复杂的 C++ 类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60205277/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com