我想从Python应用程序调用C库。我不想包装整个API,只包装与我的案例相关的函数和数据类型。在我看来,我有三个选择:

用c语言创建一个实际的扩展模块,这可能有点过分,而且我还想避免学习编写扩展的开销。 使用Cython将相关部分从C库公开到Python。 在Python中完成所有的事情,使用ctypes与外部库通信。

我不知道2)还是3)哪个更好。3)的优点是ctypes是标准库的一部分,生成的代码将是纯Python——尽管我不确定这个优点到底有多大。

这两种选择有更多的优点/缺点吗?你推荐哪种方法?


编辑:感谢你的回答,它们为任何想做类似事情的人提供了很好的资源。当然,这个决定仍然是针对单一情况做出的——没有一个“这是正确的事情”之类的答案。对于我自己的情况,我可能会使用ctypes,但我也期待在其他一些项目中尝试Cython。

由于没有唯一的正确答案,接受一个答案就有些武断了;我选择了FogleBird的答案,因为它提供了一些关于ctypes的很好的见解,而且它也是目前投票最多的答案。然而,我建议阅读所有的答案,以获得一个良好的概述。

再次感谢。


当前回答

我再举一个例子:SWIG

它很容易学,做了很多正确的事情,并支持更多的语言,所以花时间学习它是非常有用的。

如果您使用SWIG,您将创建一个新的python扩展模块,但是SWIG将为您完成大部分繁重的工作。

其他回答

就我个人而言,我会用C写一个扩展模块。不要被Python C扩展吓倒——它们写起来一点都不难。文档是非常清晰和有用的。当我第一次用Python写一个C扩展时,我想我花了大约一个小时来弄清楚如何写一个——根本没有多少时间。

如果您的目标是Windows并选择包装一些专有的c++库,那么您可能很快就会发现msvcrt***.dll (Visual c++ Runtime)的不同版本略有不兼容。

这意味着您可能无法使用Cython,因为产生了包装器。pyd链接到msvcr90.dll (Python 2.7)或msvcr100.dll (Python 3.x)。如果您正在包装的库链接到不同版本的运行时,那么您就不走运了。

然后,为了使事情正常工作,您需要为c++库创建C包装器,将包装器dll链接到与您的c++库相同版本的msvcrt***.dll。然后使用ctypes在运行时动态加载你的手卷包装器dll。

所以有很多小细节,在下面的文章中有详细的描述:

美丽的本地库(Python): http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/

当你已经有一个编译好的库blob要处理(比如OS库)时,ctypes非常有用。然而,调用开销很严重,所以如果您将对库进行大量调用,并且无论如何都要编写C代码(或者至少编译它),那么我建议您使用cython。这并不需要做更多的工作,而且使用生成的pyd文件会更快、更python化。

我个人倾向于使用cython来快速加速python代码(循环和整数比较是cython特别擅长的两个领域),当涉及到其他库的一些更复杂的代码/包装时,我将转向Boost.Python。提振。Python的设置可能很挑剔,但一旦你让它工作了,它就可以简单地包装C/ c++代码。

cython在包装numpy方面也很出色(这是我从SciPy 2009会议中了解到的),但我没有使用过numpy,因此不能对此进行评论。

要从Python应用程序调用C库,还有cffi,这是ctypes的新替代方案。它为FFI带来了全新的面貌:

它以一种迷人的、干净的方式处理问题(与ctypes相反) 它不需要编写非Python代码(如SWIG, Cython,…)

如果你已经有了一个定义了API的库,我认为ctypes是最好的选择,因为你只需要做一点初始化,然后或多或少地以你习惯的方式调用库。

我认为当你需要新代码时,Cython或用C创建一个扩展模块(这并不难)更有用,例如调用那个库并执行一些复杂、耗时的任务,然后将结果传递给Python。

对于简单的程序,另一种方法是直接执行不同的进程(外部编译),将结果输出到标准输出,并使用subprocess模块调用它。有时这是最简单的方法。

例如,如果你制作一个控制台C程序,或多或少地以这种方式工作

$miCcode 10
Result: 12345678

你可以从Python中调用它

>>> import subprocess
>>> p = subprocess.Popen(['miCcode', '10'], shell=True, stdout=subprocess.PIPE)
>>> std_out, std_err = p.communicate()
>>> print std_out
Result: 12345678

通过一些字符串格式化,您可以以任何您想要的方式获取结果。您还可以捕获标准错误输出,因此非常灵活。