用ctypes包装C++库是个坏主意吗?
我看了关于用Python封装C库和C++库的两个讨论,但我还是不太明白。我正在使用的C++库确实用了类和模板,但并没有特别复杂。用ctypes来封装它有什么问题或者注意事项吗?(除了你可以用纯Python来做等等)
还有,大家提到的PyCXX、Cython和boost::python这三种选择,哪一个更适合C++呢?
谢谢
奥利弗
2 个回答
为了支持 boost::python
,我们来看看亚历山大的关于ctypes的回答:
Boost python 提供了一个非常“C++”的接口,让 C++ 和 Python 代码可以很方便地互相交流。比如,允许 Python 的子类重写 C++ 类的虚方法,这个过程相对简单。以下是一些它的好处:
- 允许 Python 的子类重写 C++ 类中的虚方法。
- 可以把
std::vector<>
和std::map<>
这些 C++ 的数据结构,和 Python 的列表和字典连接起来(使用vector_indexing_suite
和map_indexing_suite
)。 - 在智能指针(比如
boost::shared_ptr
等)中,自动共享引用计数和 Python 的引用计数(而且你可以把这个扩展到任何智能指针)。 - 在传递参数和返回值时,可以精细控制所有权。
总的来说,如果你想以一种忠实于语言的方式来展示 C++ 接口,那么 boost::python 可能是最好的选择。
唯一的缺点是编译时间会增加(因为 boost::python 大量使用模板),而且如果你没有完全搞对,有时会出现不太清晰的错误信息。
对于C++来说,如果想让一个库能被Python使用,它必须使用C语言的导出名称。这就是说,如果有一个叫foo
的函数,它在ctypes中也能直接用foo
来访问。
要做到这一点,必须把公共接口放在export C {}
里面,这样就不允许在里面使用函数重载和模板(这里的公共接口是重点,库内部的实现可以随意使用C++的特性)。
这样做的原因是,C++编译器会用一种叫做名称修饰的机制来生成独特的名称,以区分重载或模板的符号。虽然ctypes
可以找到一个函数,只要你知道它的修饰名称,但这个修饰的规则依赖于你使用的编译器和链接器,所以你不能指望它是稳定的。简单来说:不要用ctypes来包装公共接口中使用C++特性的库。
Cython
则采取了不同的方法。它帮助你构建一个C扩展模块,来与原始库进行交互。因此,链接到C++库是通过常规的C++链接机制来完成的,这样就避免了前面提到的问题。Cython的麻烦在于,C扩展库需要为每个平台重新编译,但这同样适用于要包装的C++库。
我个人认为,在大多数情况下,使用Cython的时间是值得的,最终会比使用ctypes更有回报(当然,对于非常简单的C接口除外)。
我对boost.python
没有经验,所以无法评论(不过我也觉得它并不是很受欢迎)。