用Python包装C库:C,Cython还是ctypes?

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

  • 在C中创建一个实际的扩展模块可能是过度的,我也想避免学习扩展写入的开销。
  • 使用Cython将C库中的相关部分公开给Python。
  • 在Python中完成整个事情,使用ctypes与外部库进行通信。
  • 我不确定2)或3)是否是更好的选择。 3)的优点是ctypes是标准库的一部分,所产生的代码将是纯Python--尽管我不确定实际的优势有多大。

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


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

    由于没有单一的真实答案,接受答案有点武断。 我选择了FogleBird的答案,因为它提供了对ctypes的一些很好的见解,而且它现在也是最高票数的答案。 不过,我建议阅读所有答案以获得良好的概述。

    再次感谢。


    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。


    警告:Cython核心开发者的意见未来。

    我几乎总是推荐Cython比ctypes。 原因是它具有更平滑的升级路径。 如果你使用ctypes,很多事情一开始就很简单,用纯Python编写FFI代码当然很酷,不需要编译,构建依赖关系等等。 但是,在某些时候,您几乎肯定会发现,您必须调用很多C库,无论是循环还是更长的一系列相互依赖的调用,并且您都希望加快速度。 这是你会注意到你不能用ctypes做到的地步。 或者,当你需要回调函数并且你发现你的Python回调代码成为瓶颈时,你想加快它并且/或者将它移到C中。 再次,你不能用ctypes做到这一点。 所以你必须在那个时候切换语言,并开始重写你的代码的一部分,可能会将你的Python / ctypes代码逆向工程化为纯C语言,从而破坏了用普通Python写代码的好处。

    使用Cython,OTOH,您可以完全自由地使包装和调用代码尽可能薄或厚。 您可以从常规Python代码中简单地调用您的C代码开始,并且Cython将它们转换为本地C调用,无需额外的调用开销,并且Python参数的转换开销极低。 当你注意到在进行C库过多昂贵的调用时,你需要更多的性能,你可以开始使用静态类型注释周围的Python代码,并让Cython将它直接优化为C。 或者,您可以开始在Cython中重新编写C代码的一部分,以避免调用,并专门化和循环地修改循环。 如果您需要快速回调,只需使用适当的签名编写函数并直接将其传递到C回调注册表。 再次,没有开销,它给你明确的C调用性能。 在Cython中,如果你真的无法获得足够快的代码,那么你仍然可以考虑用C(或C ++或Fortran)重写它的真正关键部分,并从你的Cython代码中自然地和本地地调用它。 但是,这真的是最后的选择,而不是唯一的选择。

    所以,ctypes很高兴做简单的事情,并快速地运行某些东西。 但是,一旦事情开始增长,您很可能会发现您从一开始就更好地使用了Cython。


    Cython本身是一个非常酷的工具,非常值得学习,并且令人惊讶地接近Python语法。 如果您使用Numpy进行任何科学计算,那么Cython就是要走的路,因为它与Numpy集成以实现快速矩阵操作。

    Cython是Python语言的超集。 你可以抛出任何有效的Python文件,它会吐出一个有效的C程序。 在这种情况下,Cython只会将Python调用映射到底层的CPython API。 这可能导致50%的加速,因为你的代码不再被解释。

    为了获得一些优化,你必须开始告诉Cython关于你的代码的额外事实,比如类型声明。 如果你足够的了解它,它可以将代码简化为纯C语言。也就是说,Python中的for循环变成了C语言中的for循环。在这里你将看到巨大的速度增益。 你也可以在这里链接到外部的C程序。

    使用Cython代码也非常容易。 我认为这本手册听起来很难。 你从字面上就是这样做的:

    $ cython mymodule.pyx
    $ gcc [some arguments here] mymodule.c -o mymodule.so
    

    然后你可以在你的Python代码中import mymodule ,并完全忘记它编译为C

    无论如何,因为Cython非常容易安装并开始使用,所以我建议您尝试一下,看它是否适合您的需求。 如果它不是你想要的工具,那不会是浪费。

    链接地址: http://www.djcxy.com/p/81317.html

    上一篇: Wrapping a C library in Python: C, Cython or ctypes?

    下一篇: Callbacks in COM objects