[英]Cython: How to wrap a C++ function that returns a C++ object?
I'm working on a Python project where I'd like to interface with a C++ package that has already been written. 我正在一个Python项目中,我想与已经编写的C ++包进行交互。 Since I'll be using Cython in other portions of this project, I'd prefer to wrap using Cython.
由于我将在该项目的其他部分中使用Cython,因此我更喜欢使用Cython进行包装。
In brief, I need to wrap a function, FooBar, that returns an object of custom class type Bar. 简而言之,我需要包装一个函数FooBar,该函数返回自定义类类型Bar的对象。
Here's the Bar.h: 这是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);
And PyBar.pyx: 和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
Actual Question: Is this even the right approach to what I want to do? 实际的问题:这甚至是我想要做的正确方法吗? For reference, if I just tried to wrap the class by itself I have had no problem: I can import the module, create objects using PyBar(), and underlying C methods implemented on the class would work.
作为参考,如果我只是尝试自己包装类,那么我没有问题:可以导入模块,使用PyBar()创建对象,并且在类上实现的基础C方法将起作用。 The issue is trying to wrap a function that returns objects of the C++ class.
问题是试图包装一个返回C ++类对象的函数。 In the wild, I'll never actually want to create PyBar representation of any Bar object that wasn't created by FooBar, so this is the approach I decided upon after much head scratching.
在野外,我永远不会真正想为任何不是由FooBar创建的Bar对象创建PyBar表示形式,因此这是我在大量抓挠之后决定采用的方法。
With respect to the first part of the problem, I think the more elegant change would be to have FooBar defined as: 关于问题的第一部分,我认为更优雅的更改是将FooBar定义为:
Bar* FooBar(const std::vector<double> & O, size_t P, size_t Q);
and have it return a "new" allocated pointer. 并返回“分配的”新指针。 I think in your original Cython code
__cinit__
you'll create a stack allocated Bar, take a pointer of that, and then that will expire resulting in eventual disaster. 我认为在您最初的Cython代码
__cinit__
您将创建一个分配了Bar的堆栈,获取该Bar的指针,然后该指针将过期,最终导致灾难。
An alternative solution that might work would be be to keep FooBar returning Bar, change PyBar so it starts 一种可行的替代解决方案是保持FooBar返回Bar,更改PyBar以使其开始
cdef class PyBar:
cdef Bar this_obj
def __cinit__(self, O, P, Q):
self.this_obj = FooBar(O,P,Q)
ie keeps an object rather than a pointer. 即保留一个对象而不是一个指针。 No
__dealloc__
should be necessary. 不需要
__dealloc__
。
I don't know about the undefined symbol error... 我不知道未定义的符号错误...
After playing with this for awhile, the only semi-elegant (emphasis on the semi) solutions I found do involve modifying the existing C++ code. 在玩了一段时间之后,我发现唯一的半优雅(强调半)的解决方案确实涉及到修改现有的C ++代码。 The approach I half-implemented in my question has many problems and should probably be ignored.
我在问题中半执行的方法有很多问题,可能应该忽略。
Maybe someone with more experience writing C++ code can come up with something better, but for the sake of posterity: 也许有更多编写C ++代码经验的人可以提出更好的建议,但是为了后代:
I personally found it easier to modify FooBar() so that it is a member function of Bar: instead of returning a Bar object, it now modifies the instance it is called from. 我个人发现修改FooBar()使其更容易成为Bar的成员函数:现在,它不再修改Bar对象,而是修改了从其调用的实例。 Then, when wrapping Bar in Cython, I do not expose FooBar() as a class method, but I do call the method in the constructor for the Python (and thus, the corresponding C++) object.
然后,将Bar包装在Cython中时,我没有将FooBar()公开为类方法,而是在Python(以及相应的C ++)对象的构造函数中调用了该方法。 This works for me because, as I stated, I really only ever intend to deal with Bar objects that have been initialized with some set of values by FooBar().
这对我有用,因为正如我所说,我实际上只打算处理由FooBar()用某些值初始化的Bar对象。
In the end, I chose this approach over using a copy constructor (which would allow me to initialize a new Python/corresponding C++ Bar object from an existing Bar object created by FooBar), because it seemed more readable to me. 最后,我选择了这种方法,而不是使用复制构造函数(这使我可以从FooBar创建的现有Bar对象中初始化一个新的Python /对应的C ++ Bar对象),因为它对我而言似乎更具可读性。 The advantage of the copy constructor approach would be that one would only have to modify the Bar class definition in C (adding a copy constructor), which might be preferable if you're truly uncomfortable about changing the implementation of FooBar().
复制构造函数方法的优点是,只需修改C中的Bar类定义(添加复制构造函数),如果您真的不满意更改FooBar()的实现,则可能更可取。 In my case, since Bar objects can sometimes contain very large vectors, a copy constructor also seemed like a bad idea for performance reasons.
就我而言,由于Bar对象有时可以包含很大的向量,因此出于性能原因,复制构造函数似乎也不是一个好主意。
#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(){}
};
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
Then, I rewrote the implementation of FooBar() in Bar.cpp, changing the return type to void and substituting the Bar result
object previously returned by the function for this
. 然后,我重写FooBar的()在Bar.cpp的实施,改变了返回类型无效,置换
Bar result
以前由该函数返回的对象this
。 For example (and being explicit in my use of this for the sake of clarity): 例如(为了清楚起见,在我的使用中明确指出):
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);
...
}
would become something like this: 会变成这样:
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);
...
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.