编译前用Python代码进行原型设计
我一直在考虑写一个峰值拟合的库。我的Python水平还不错,打算先用Python来实现所有功能,但我想最终可能需要用编译语言重新实现一些核心的部分。
如果我没记错的话,Python最初的设计就是作为一种原型语言。不过,Python在允许将函数、函数对象和对象传递给其他函数和方法方面非常灵活,而我怀疑像C或Fortran这样的语言就没有这么方便了。
我需要了解哪些关于设计函数和类的知识,以便它们能与编译语言进行交互?这些潜在的问题有多少是通过像cTypes、bgen、SWIG、Boost.Python、Cython或Python SIP这样的库来解决的?
对于这个特定的用例(一个拟合库),我想让用户能够定义数学函数(比如高斯函数、洛伦兹函数等),这些函数可以作为Python函数传递给编译的拟合库进行解释。传递和返回数组也是非常重要的。
7 个回答
如果你打算将代码转成编译后的版本,最好的方法是把那些对性能要求高的部分写成一组简单的函数模块,采用一种叫做函数式编程的风格。这种风格的特点是没有状态,也没有副作用,函数只接受和返回基本的数据类型。
这样做可以让你在Python原型代码和最终的编译代码之间建立一一对应的关系,也能让你更轻松地使用ctypes,避免很多麻烦。
如果你要进行峰值拟合,几乎肯定需要使用数组,这会让事情稍微复杂一些,但用ctypes还是很可行的。
如果你真的想使用更复杂的数据结构,或者需要修改传入的参数,SWIG或者Python的标准C扩展接口可以帮助你实现这些功能,不过会有一些麻烦。
根据你的需求,你可能还想看看NumPy,它可以处理一些你想转给C的工作,并且提供一些额外的帮助,让数据在Python和C之间传输更方便。
我没有用过SWIG或SIP,但我觉得用boost.python写Python的包装器非常强大,而且相对简单易用。
我不太清楚你在C/C++和Python之间传递类型的具体需求,不过你可以很容易地通过将C++类型暴露给Python,或者在你的C++ API中使用一个通用的boost::python::object参数来实现。你还可以注册转换器,自动将Python类型转换为C++类型,反之亦然。
如果你打算使用boost.python,这个教程是个不错的起点。
我实现过一些和你需要的功能类似的东西。我有一个C++函数,它接受一个Python函数和一张图像作为参数,并将这个Python函数应用到图像的每一个像素上。
Image* unary(boost::python::object op, Image& im)
{
Image* out = new Image(im.width(), im.height(), im.channels());
for(unsigned int i=0; i<im.size(); i++)
{
(*out)[i] == extract<float>(op(im[i]));
}
return out;
}
在这个例子中,Image是一个暴露给Python的C++对象(一个像素值为浮点数的图像),而op是一个Python定义的函数(或者说是任何有__call__属性的Python对象)。你可以像下面这样使用这个函数(假设unary在被调用的图像中,并且这个图像也包含Image和一个加载函数):
import image
im = image.load('somefile.tiff')
double_im = image.unary(lambda x: 2.0*x, im)
至于使用boost处理数组,我个人没有这样做过,但我知道boost提供了将数组暴露给Python的功能——这个链接可能会对你有帮助。
终于有一个我可以给出具体答案的问题了!
我研究过f2py、boost.python、swig、cython和pyrex,主要是为了我的工作(光学测量技术的博士研究)。我广泛使用了swig,偶尔用boost.python,也用了很多pyrex和cython。我还用过ctypes。以下是我的总结:
声明: 这是我的个人经验。我与这些项目没有任何关系。
swig: 和C++的配合不太好。理论上应该可以,但在链接的过程中出现了名称混淆的问题,这让我在Linux和Mac OS X上非常头疼。如果你有C代码想要和Python连接,这个工具是个不错的选择。我为我的需求封装了GTS,基本上需要写一个C共享库来连接。我不太推荐这个。
Ctypes: 我用ctypes写了一个libdc1394(IEEE相机库)的封装,过程非常简单。你可以在https://launchpad.net/pydc1394找到代码。把头文件转换成Python代码需要很多工作,但之后一切都能可靠运行。如果你想连接一个外部库,这个方法很好。ctypes也在Python的标准库中,所以每个人都可以立即使用你的代码。这也是快速尝试新库的好方法。我推荐用它来连接外部库。
Boost.Python: 非常愉快。如果你已经有自己的C++代码想在Python中使用,那就选这个。把C++类结构转换成Python类结构非常简单。如果你需要在Python中使用C++代码,我推荐这个。
Pyrex/Cython: 用Cython,不要用Pyrex。就这样。Cython更先进,使用起来更愉快。现在,我用Cython做所有以前用SWIG或Ctypes做的事情。如果你的Python代码运行得太慢,Cython是最好的选择。这个过程非常棒:你把Python模块转换成Cython模块,构建它们,然后继续像以前一样进行性能分析和优化(不需要更换工具)。你可以根据需要将C代码和Python代码混合使用。这比重写整个应用程序的很多部分要快得多;你只需要重写内部循环。
时间开销: ctypes的调用开销最高(约700纳秒),其次是boost.python(322纳秒),然后是swig(290纳秒)。Cython的调用开销最低(124纳秒),并且提供了最好的反馈,告诉你时间花在哪里了(支持cProfile!)。这些数字是我在一个交互式shell中调用一个返回整数的简单函数时得到的;模块导入的开销没有计入,只有函数调用的开销被计算。因此,通过性能分析和使用Cython,快速获取Python代码是最简单和最有效的。
总结: 对于你的问题,使用Cython ;)。希望这个总结对一些人有帮助。如果还有其他问题,我很乐意回答。
编辑: 我忘了提:如果是数值计算(也就是连接NumPy),使用Cython;它们对此有支持(因为他们基本上是为了这个目的开发Cython的)。所以这应该是你决定的另一个加分项。