为了账号安全,请及时绑定邮箱和手机立即绑定

在Python中包装C库:C,Cython或ctypes?

在Python中包装C库:C,Cython或ctypes?

largeQ 2019-10-12 14:43:48
我想从Python应用程序调用C库。我不想包装整个API,只包装与我的情况相关的函数和数据类型。如我所见,我有三个选择:在C中创建一个实际的扩展模块。可能有点过头了,我还想避免学习扩展编写的开销。使用Cython将C库的相关部分公开给Python。使用Python ctypes与外部库进行通信,从而完成整个工作。我不确定2)还是3)是更好的选择。3)的优点是它ctypes是标准库的一部分,并且生成的代码将是纯Python –尽管我不确定该优点实际上有多大。两种选择都有其他优点/缺点吗?您推荐哪种方法?编辑:感谢您的所有回答,它们为希望做类似事情的任何人提供了很好的资源。当然,仍需针对单个案例做出决定-没有人会回答“这是对的”。就我自己而言,我可能会使用ctypes,但我也期待在其他项目中试用Cython。由于没有一个单一的真实答案,因此接受一个答案有些武断。我选择了FogleBird的答案,因为它提供了对ctypes的一些很好的了解,并且它也是当前投票最高的答案。但是,我建议您阅读所有答案以获得一个很好的概述。再次感谢。
查看完整描述

3 回答

?
九州编程

TA贡献1785条经验 获得超4个赞

ctypes 是快速完成它的最佳选择,并且在仍在编写Python的情况下很高兴与您合作!


我最近包装了一个FTDI驱动程序,用于使用ctypes与USB芯片通信,这很棒。我完成了所有工作,并在不到一天的时间内完成工作。(我只实现了我们需要的功能,大约有15个功能)。


出于同一目的,我们以前使用的是第三方模块PyUSB。PyUSB是实际的C / Python扩展模块。但是PyUSB在阻止读写操作时没有释放GIL,这给我们带来了问题。因此,我使用ctypes编写了自己的模块,该模块在调用本机函数时会释放GIL。


需要注意的一件事是,ctypes不会知道#define所使用的库中的常量和内容,而仅是函数,因此您必须在自己的代码中重新定义这些常量。


这是一个代码最终的外观示例(大量内容被删除,只是试图向您展示其要旨):


from ctypes import *


d2xx = WinDLL('ftd2xx')


OK = 0

INVALID_HANDLE = 1

DEVICE_NOT_FOUND = 2

DEVICE_NOT_OPENED = 3


...


def openEx(serial):

    serial = create_string_buffer(serial)

    handle = c_int()

    if d2xx.FT_OpenEx(serial, OPEN_BY_SERIAL_NUMBER, byref(handle)) == OK:

        return Handle(handle.value)

    raise D2XXException


class Handle(object):

    def __init__(self, handle):

        self.handle = handle

    ...

    def read(self, bytes):

        buffer = create_string_buffer(bytes)

        count = c_int()

        if d2xx.FT_Read(self.handle, buffer, bytes, byref(count)) == OK:

            return buffer.raw[:count.value]

        raise D2XXException

    def write(self, data):

        buffer = create_string_buffer(data)

        count = c_int()

        bytes = len(data)

        if d2xx.FT_Write(self.handle, buffer, bytes, byref(count)) == OK:

            return count.value

        raise D2XXException

有人对各种选项做了一些基准测试。


如果不得不包装带有许多类/模板/等的C ++库,我可能会更加犹豫。但是ctypes可以很好地与结构配合使用,甚至可以回调到Python中。



查看完整回答
反对 回复 2019-10-12
?
桃花长相依

TA贡献1860条经验 获得超8个赞

警告:Cython核心开发人员的意见。

我几乎总是建议Cython胜过ctypes。原因是它具有更平滑的升级路径。如果使用ctypes,一开始很多事情都会很简单,用纯Python编写FFI代码当然很酷,而无需编译,构建依赖关系以及所有这些。但是,在某个时候,您几乎肯定会发现,您必须循环或以更长的一系列相互依赖的调用方式大量调用C库,并且您希望加快速度。在这一点上,您会注意到无法使用ctypes做到这一点。或者,当您需要回调函数并且发现您的Python回调代码成为瓶颈时,您也想加快它的速度和/或也将它移入C。同样,您不能使用ctypes做到这一点。

使用OTOH的Cython,您可以完全自由地使包装和调用代码变薄或变厚。您可以从对常规Python代码的C代码中进行简单的调用开始,然后Cython会将它们转换为本地C调用,而没有任何额外的调用开销,并且Python参数的转换开销非常低。当您发现需要对C库进行太多昂贵的调用时,甚至在某些时候需要更高的性能时,可以开始使用静态类型注释周围的Python代码,并让Cython为您直接对其进行优化。或者,您可以开始在Cython中重写部分C代码,以避免调用并在算法上专门化和加强循环。如果您需要快速回调,只需编写具有适当签名的函数,然后将其直接传递到C回调注册表即可。同样,没有开销,并且它为您提供普通的C调用性能。而且在不太可能发生的情况下,您实际上无法在Cython中获得足够快的代码,您仍然可以考虑用C(或C ++或Fortran)重写其真正关键的部分,并自然地从本地从Cython代码中调用它。但是,这实际上成了最后的选择,而不是唯一的选择。

因此,ctypes非常适合做简单的事情并快速使某些事情运行。但是,一旦事情开始发展,您很可能会发现您最好从一开始就使用Cython。


查看完整回答
反对 回复 2019-10-12
  • 3 回答
  • 0 关注
  • 1098 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信