使用C/neneneba API和C++类编写Python模块

Writing a Python module using C/API and C++ classes

本文关键字:Python 模块 C++ neneneba API 使用      更新时间:2023-10-16

我是编写自定义Python模块的新手,对Capsules的工作方式有点困惑。我使用系统OSX安装中的Python 2.7.6,并尝试使用Capsules(对于Python>2.7推荐使用)来传递指针(在他们使用PyCObject之前)。我的代码目前不起作用,我想在这里了解一下原则上应该如何处理。代码应该定义一个类LuscherClm,我希望能够做到以下几点:

>>> c40=Luscher(4,0)
>>>
>>> c40(0.12)
>>> <print the result of the evaluation>

第一个问题:目前我必须做一些类似的事情:

>>> c40=Luscher.init(4,0)
>>>
>>> c40.eval(0.12)
Segfault 

因此,我的第一个问题是:如何修改方法表,使其具有更多运算符样式的强制转换,而不是成员函数init和eval。

然而,我的代码还有其他问题,这里是相关的部分(底层C++类工作顺利,我在生产中经常使用它):

析构函数:

//destructor
static void clm_destruct(PyObject* capsule){
void* ptr=PyCapsule_GetPointer(capsule,"zetfunc");
Zetafunc* zetptr=static_cast<Zetafunc*>(ptr);
delete zetptr;
return;
}

构造函数:它返回指向胶囊的指针。我不知道这是否正确。因为在这种情况下,当我调用clm=LuscherClm.init(l,m)时,clm对象是PyCapsule,并且没有属性eval,所以我不能对此调用clm.eval(x)。应该如何处理?

//constructor
static PyObject* clm_init(PyObject* self, PyObject *args){
//return value
PyObject* result=NULL;
//parse variables
unsigned int lval=0;
int mval=0;
if(!PyArg_ParseTuple(args,"li",&lval,&mval)){
::std::cout << "Please specify l and m!" << ::std::endl;
return result;
}
//class instance:
Zetafunc* zetfunc=new Zetafunc(lval,mval);
instanceCapsule=PyCapsule_New(static_cast<void*>   (zetfunc),"zetfunc",&clm_destruct);
return instanceCapsule;
}

那么,胶囊是如何传递给评估功能的呢?下面的代码是不正确的,因为我从CObjects移动到Capsules后没有更新它。胶囊应该是一个全局变量吗(我不喜欢这样),或者我如何将其传递给评估函数?或者我应该称之为自我,但此刻的自我是什么?

//evaluate the function
static PyObject* clm_evaluate(PyObject* self, PyObject* args){
//get the PyCObject from the capsule:
void* tmpzetfunc=PyCapsule_GetPointer(instanceCapsule,"zetfunc");
if (PyErr_Occurred()){
std::cerr << "Some Error occured!" << std::endl;
return NULL;
}
Zetafunc* zetfunc=static_cast< Zetafunc* >(tmpzetfunc);
//parse value:
double x;
if(!PyArg_ParseTuple(args,"d",&x)){
std::cerr << "Specify a number at which you want to evaluate the function" << std::endl;
return NULL;
}
double result=(*zetfunc)(x).re();
//return the result as a packed function:
return Py_BuildValue("d",result);
}
//methods
static PyMethodDef LuscherClmMethods[] = {
{"init",  clm_init, METH_VARARGS, "Initialize clm class!"},
{"eval", clm_evaluate, METH_VARARGS, "Evaluate the Zeta-Function!"},
{NULL, NULL, 0, NULL}        /* Sentinel */
};

Python<3初始化功能:

PyMODINIT_FUNC
initLuscherClm(void)
{
PyObject *m = Py_InitModule("LuscherClm", LuscherClmMethods);
return;
}

你能向我解释一下出了什么问题,为什么?如果可能的话,我想远离SWIG或boost,因为这个模块应该很容易携带,我想避免每次在其他地方使用它时都必须安装额外的软件包。进一步:C/neneneba API在调用函数时产生的开销是多少?我需要称之为O(10^6)次的顺序,我仍然希望它是快的。

好的,我现在使用boost.python,但当我运行object.eval()时,我得到了一个segfault。这就是我现在的过程:

BOOST_PYTHON_MODULE(threevecd)
{
class_< threevec<double> >("threevecd",init<double,double,double>());
}
BOOST_PYTHON_MODULE(LuscherClm)
{
class_<Zetafunc>("LuscherClm",init<int,int, optional<double,threevec<double>,double,int> >())
.def("eval",&Zetafunc::operator(),return_value_policy<return_by_value>());
boost::python::to_python_converter<dcomplex,dcomplex_to_python_object>();
}

dcomplex是我自己的复数实现。所以我不得不写一个转换器:

struct dcomplex_to_python_object
{
static PyObject* convert(dcomplex const& comp)
{
if(fabs(comp.im())<std::numeric_limits<double>::epsilon()){
boost::python::object result=boost::python::object(complex<double>(comp.re(),comp.im()));
return boost::python::incref(result.ptr());
}
else{
return Py_BuildValue("d",comp.re());
}
}
};

Complex128是boost无法理解的numpy扩展。所以我的问题是:1) 如何将复数作为python数据类型返回(complex是标准的python类型吗?)2) 为什么我会有一个segfault。我在测试用例中的结果是真实的,所以它应该默认为else语句。我猜指针超出了范围,就是这样。但即使在if的情况下(我注意ref的增量),它也会出错。有人能帮我解决类型转换问题吗?

谢谢Thorsten

好的,我明白了。下面的转换器完成了任务:

struct dcomplex_to_python_object
{
static PyObject* convert(dcomplex const& comp)
{
PyObject* result;
if(std::abs(comp.im())<=std::numeric_limits<double>::epsilon()){
result=PyFloat_FromDouble(comp.re());
}
else{
result=PyComplex_FromDoubles(comp.re(),comp.im());
}
Py_INCREF(result);
return result;
}
};

使用这个转换器和沃特的帖子,我想我的问题已经得到了回答。感谢