Cython:如何包装 returns C++ 对象的 C++ 函数?
Cython: How to wrap a C++ function that returns a C++ object?
我正在做一个 Python 项目,我想在其中与已经编写的 C++ 包进行交互。由于我将在该项目的其他部分使用 Cython,因此我更愿意使用 Cython 进行包装。
简而言之,我需要包装一个函数 FooBar,它 returns 一个自定义 class 类型 Bar 的对象。
这是Bar.h:
#include <cstddef> // For size_t
#include <vector>
/* data returned by function FooBar()*/
class Bar {
public:
size_t X;
std::vector<size_t> Y;
std::vector<double> Z;
std::vector<double> M;
std::vector<size_t> N;
};
Bar FooBar(const std::vector<double> & O, size_t P, size_t Q);
和PyBar.pyx:
from libcpp.vector cimport vector
cdef extern from "Bar.h":
cdef cppclass Bar:
size_t X
vector[size_t] Y
vector[double] Z
vector[double] M
vector[size_t] N
cdef Bar FooBar(const vector[double] & O, size_t P, size_t Q)
cdef class PyBar:
cdef Bar *thisptr # hold a C++ instance which we're wrapping
def __cinit__(self, O, P, Q):
C_Bar = FooBar(O, P, Q)
self.thisptr = &C_Bar
def __dealloc__(self):
del self.thisptr
实际问题:这是否是我想做的事情的正确方法?作为参考,如果我只是尝试自己包装 class 我没有问题:我可以导入模块,使用 PyBar() 创建对象,并且在 class 上实现的底层 C 方法会起作用.问题是试图包装一个 returns C++ class 对象的函数。在野外,我永远不会真正想要创建不是由 FooBar 创建的任何 Bar 对象的 PyBar 表示,所以这是我在多次挠头之后决定的方法。
玩了一段时间后,我发现唯一的半优雅(强调半)解决方案涉及修改现有的 C++ 代码。我在问题中实施了一半的方法有很多问题,可能应该被忽略。
也许有更多编写 C++ 代码经验的人可以想出更好的东西,但为了后代:
我个人发现修改 FooBar() 使其成为 Bar 的成员函数更容易:它现在修改调用它的实例,而不是 return 一个 Bar 对象。然后,当在 Cython 中包装 Bar 时,我不会将 FooBar() 公开为 class 方法,但我会在构造函数中为 Python(以及相应的 C++)对象调用该方法。这对我有用,因为正如我所说,我真的只打算处理已由 FooBar() 使用某些值集初始化的 Bar 对象。
最后,我选择了这种方法而不是使用复制构造函数(这将允许我从 FooBar 创建的现有 Bar 对象初始化一个新的 Python/corresponding C++ Bar 对象),因为它看起来更具可读性大部头书。复制构造函数方法的优点是,只需修改 C 中的 Bar class 定义(添加复制构造函数),如果您真的对更改 FooBar( ).就我而言,由于 Bar 对象有时可能包含非常大的向量,因此出于性能原因,复制构造函数似乎也不是一个好主意。
这是我的最终代码:
Bar.h:
#include <cstddef> // For size_t
#include <vector>
class Bar {
public:
size_t X;
std::vector<size_t> Y;
std::vector<double> Z;
std::vector<double> M;
std::vector<size_t> N;
void FooBar(const std::vector<double> & O, size_t P, size_t Q);
ClusterResult(){}
};
PyBar.pyx:
from libcpp.vector cimport vector
cdef extern from "Bar.h":
cdef cppclass Bar:
size_t X
vector[size_t] Y
vector[double] Z
vector[double] M
vector[size_t] N
Bar()
void FooBar(const vector[double] & O, size_t P, size_t Q)
cdef class PyBar:
cdef Bar *thisptr # hold a C++ instance which we're wrapping
def __cinit__(self, O, P, Q):
self.thisptr = new Bar()
self.thisptr.FooBar(O, P, Q)
def __dealloc__(self):
del self.thisptr
#Below, I implement the public attributes as get/setable properties.
#could have written get/set functions, but this seems more Pythonic.
property X:
def __get__(self): return self.thisptr.X
def __set__(self, X): self.thisptr.X = X
property Y:
def __get__(self): return self.thisptr.Y
def __set__(self, Y): self.thisptr.Y = Y
property Z:
def __get__(self): return self.thisptr.Z
def __set__(self, Z): self.thisptr.centers = Z
property M:
def __get__(self): return self.thisptr.M
def __set__(self, size): self.thisptr.M = M
property N:
def __get__(self): return self.thisptr.N
def __set__(self, size): self.thisptr.N = N
重构 FooBar() 实现:
然后,我在 Bar.cpp 中重写了 FooBar() 的实现,将 return 类型更改为 void 并替换之前由 return 编辑的 Bar result
对象this
的功能。例如(为了清楚起见,在我的使用中明确说明):
Bar FooBar(const std::vector<double> & O, size_t P, size_t Q)
{
Bar result = new Bar();
result.X = P + 1;
result.Z = std::sort(O.begin()+1, O.end());
const size_t newParam = Q + 2;
someOtherFunction(newParam, result);
...
}
会变成这样:
void Bar::FooBar(const std::vector<double> & O, size_t P, size_t Q)
{
this->X = P + 1;
this->Z = std::sort(O.begin()+1, O.end());
const size_t newParam = Q + 2;
someOtherFunction(newParam, *this);
...
}
关于问题的第一部分,我认为更优雅的变化是将 FooBar 定义为:
Bar* FooBar(const std::vector<double> & O, size_t P, size_t Q);
并让它 return 一个 "new" 分配的指针。我认为在您的原始 Cython 代码中 __cinit__
您将创建一个堆栈分配的 Bar,获取它的指针,然后它会过期导致最终的灾难。
另一种可行的解决方案是保留 FooBar returning Bar,更改 PyBar 使其启动
cdef class PyBar:
cdef Bar this_obj
def __cinit__(self, O, P, Q):
self.this_obj = FooBar(O,P,Q)
即保留一个对象而不是一个指针。不需要 __dealloc__
。
我不知道未定义符号错误...
我正在做一个 Python 项目,我想在其中与已经编写的 C++ 包进行交互。由于我将在该项目的其他部分使用 Cython,因此我更愿意使用 Cython 进行包装。
简而言之,我需要包装一个函数 FooBar,它 returns 一个自定义 class 类型 Bar 的对象。
这是Bar.h:
#include <cstddef> // For size_t
#include <vector>
/* data returned by function FooBar()*/
class Bar {
public:
size_t X;
std::vector<size_t> Y;
std::vector<double> Z;
std::vector<double> M;
std::vector<size_t> N;
};
Bar FooBar(const std::vector<double> & O, size_t P, size_t Q);
和PyBar.pyx:
from libcpp.vector cimport vector
cdef extern from "Bar.h":
cdef cppclass Bar:
size_t X
vector[size_t] Y
vector[double] Z
vector[double] M
vector[size_t] N
cdef Bar FooBar(const vector[double] & O, size_t P, size_t Q)
cdef class PyBar:
cdef Bar *thisptr # hold a C++ instance which we're wrapping
def __cinit__(self, O, P, Q):
C_Bar = FooBar(O, P, Q)
self.thisptr = &C_Bar
def __dealloc__(self):
del self.thisptr
实际问题:这是否是我想做的事情的正确方法?作为参考,如果我只是尝试自己包装 class 我没有问题:我可以导入模块,使用 PyBar() 创建对象,并且在 class 上实现的底层 C 方法会起作用.问题是试图包装一个 returns C++ class 对象的函数。在野外,我永远不会真正想要创建不是由 FooBar 创建的任何 Bar 对象的 PyBar 表示,所以这是我在多次挠头之后决定的方法。
玩了一段时间后,我发现唯一的半优雅(强调半)解决方案涉及修改现有的 C++ 代码。我在问题中实施了一半的方法有很多问题,可能应该被忽略。
也许有更多编写 C++ 代码经验的人可以想出更好的东西,但为了后代:
我个人发现修改 FooBar() 使其成为 Bar 的成员函数更容易:它现在修改调用它的实例,而不是 return 一个 Bar 对象。然后,当在 Cython 中包装 Bar 时,我不会将 FooBar() 公开为 class 方法,但我会在构造函数中为 Python(以及相应的 C++)对象调用该方法。这对我有用,因为正如我所说,我真的只打算处理已由 FooBar() 使用某些值集初始化的 Bar 对象。
最后,我选择了这种方法而不是使用复制构造函数(这将允许我从 FooBar 创建的现有 Bar 对象初始化一个新的 Python/corresponding C++ Bar 对象),因为它看起来更具可读性大部头书。复制构造函数方法的优点是,只需修改 C 中的 Bar class 定义(添加复制构造函数),如果您真的对更改 FooBar( ).就我而言,由于 Bar 对象有时可能包含非常大的向量,因此出于性能原因,复制构造函数似乎也不是一个好主意。
这是我的最终代码:
Bar.h:
#include <cstddef> // For size_t
#include <vector>
class Bar {
public:
size_t X;
std::vector<size_t> Y;
std::vector<double> Z;
std::vector<double> M;
std::vector<size_t> N;
void FooBar(const std::vector<double> & O, size_t P, size_t Q);
ClusterResult(){}
};
PyBar.pyx:
from libcpp.vector cimport vector
cdef extern from "Bar.h":
cdef cppclass Bar:
size_t X
vector[size_t] Y
vector[double] Z
vector[double] M
vector[size_t] N
Bar()
void FooBar(const vector[double] & O, size_t P, size_t Q)
cdef class PyBar:
cdef Bar *thisptr # hold a C++ instance which we're wrapping
def __cinit__(self, O, P, Q):
self.thisptr = new Bar()
self.thisptr.FooBar(O, P, Q)
def __dealloc__(self):
del self.thisptr
#Below, I implement the public attributes as get/setable properties.
#could have written get/set functions, but this seems more Pythonic.
property X:
def __get__(self): return self.thisptr.X
def __set__(self, X): self.thisptr.X = X
property Y:
def __get__(self): return self.thisptr.Y
def __set__(self, Y): self.thisptr.Y = Y
property Z:
def __get__(self): return self.thisptr.Z
def __set__(self, Z): self.thisptr.centers = Z
property M:
def __get__(self): return self.thisptr.M
def __set__(self, size): self.thisptr.M = M
property N:
def __get__(self): return self.thisptr.N
def __set__(self, size): self.thisptr.N = N
重构 FooBar() 实现:
然后,我在 Bar.cpp 中重写了 FooBar() 的实现,将 return 类型更改为 void 并替换之前由 return 编辑的 Bar result
对象this
的功能。例如(为了清楚起见,在我的使用中明确说明):
Bar FooBar(const std::vector<double> & O, size_t P, size_t Q)
{
Bar result = new Bar();
result.X = P + 1;
result.Z = std::sort(O.begin()+1, O.end());
const size_t newParam = Q + 2;
someOtherFunction(newParam, result);
...
}
会变成这样:
void Bar::FooBar(const std::vector<double> & O, size_t P, size_t Q)
{
this->X = P + 1;
this->Z = std::sort(O.begin()+1, O.end());
const size_t newParam = Q + 2;
someOtherFunction(newParam, *this);
...
}
关于问题的第一部分,我认为更优雅的变化是将 FooBar 定义为:
Bar* FooBar(const std::vector<double> & O, size_t P, size_t Q);
并让它 return 一个 "new" 分配的指针。我认为在您的原始 Cython 代码中 __cinit__
您将创建一个堆栈分配的 Bar,获取它的指针,然后它会过期导致最终的灾难。
另一种可行的解决方案是保留 FooBar returning Bar,更改 PyBar 使其启动
cdef class PyBar:
cdef Bar this_obj
def __cinit__(self, O, P, Q):
self.this_obj = FooBar(O,P,Q)
即保留一个对象而不是一个指针。不需要 __dealloc__
。
我不知道未定义符号错误...