代码之家  ›  专栏  ›  技术社区  ›  balpha

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

  •  255
  • balpha  · 技术社区  · 15 年前

    1. 在C中创建一个实际的扩展模块。可能有些过火了,我还想避免学习扩展写作的开销。
    2. 使用 Cython
    3. 在Python中使用 ctypes 与外部图书馆沟通。

    是标准库的一部分,生成的代码将是纯Python——尽管我不确定这一优势到底有多大。


    编辑:

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

    11 回复  |  直到 15 年前
        1
  •  120
  •   FogleBird    15 年前

    ctypes 是您快速完成任务的最佳选择,在您仍在编写Python时使用它是一种乐趣!

    我最近包了一个 FTDI

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

    #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
    

    有人这样做了 some benchmarks

    如果我不得不用大量的类/模板来包装C++库,我可能会更加犹豫,但是cType与结构非常好,甚至可以。 callback 变成Python。

        2
  •  165
  •   Stefan Behnel    13 年前

    警告:Cython core开发者的意见在前面。

    因此,ctypes很适合做简单的事情并快速运行。然而,一旦事情开始发展,你很可能会注意到你最好从一开始就使用Cython。

        3
  •  102
  •   carl    15 年前

    为了获得一些优化,您必须开始告诉Cython关于代码的其他事实,例如类型声明。如果你告诉它足够多,它可以把代码归结为纯C。也就是说,Python中的for循环变成了C中的for循环。在这里你会看到巨大的速度提升。您也可以在此处链接到外部C程序。

    使用Cython代码也非常简单。我觉得这本手册听起来很难。你实际上只是做:

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

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

    无论如何,因为Cython很容易安装和开始使用,我建议试试看它是否适合您的需要。如果它不是你要找的工具,那就不会是浪费。

        4
  •  42
  •   Robert Zaremba    12 年前

    cffi 这是一个新的选择 ctypes

    • 它以一种引人入胜的、干净的方式处理问题(与之相反) ctypes
    • 它不需要编写非Python代码(如 斯威格,赛顿 , ...)
        5
  •  21
  •   Chris Arguin    15 年前

    我再扔一个: SWIG

        6
  •  18
  •   mipadi    15 年前

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

        7
  •  11
  •   Ryan Ginstrom    15 年前

    ctypes 当您已经有一个编译过的库blob要处理时(例如OS库),这是非常好的。然而,调用开销非常大,因此如果您要对库进行大量调用,并且无论如何都要编写C代码(或者至少要编译它),我会说 cython

    Boost.Python . Python的设置可能会很挑剔,但一旦您让它工作起来,它就可以让包装C/C++代码变得简单。

    cython也擅长包装 numpy (这是我从 SciPy 2009 proceedings ),但我还没有用过numpy,所以我不能对此发表评论。

        8
  •  11
  •   Gilles 'SO- stop being evil'    13 年前

    ctypes 是最好的选择,因为您只需执行一点初始化,然后或多或少地按照您习惯的方式调用库。

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

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

    例如,如果你制作了一个控制台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
    

        9
  •  8
  •   Misha    11 年前

    使用ctypes,结果根本不取决于您使用的编译器。您可以或多或少地使用任何可以编译为本机共享库的语言编写库。哪种系统、哪种语言、哪种编译器都无关紧要。然而,Cython受到基础设施的限制。例如,如果你想在windows上使用英特尔编译器,那么让cython工作起来就要复杂得多:你应该向cython“解释”编译器,用这个编译器重新编译一些东西,等等。这大大限制了可移植性。

        10
  •  4
  •   iljau    11 年前

    如果你瞄准Windows并选择包装一些专有的C++库,那么你可能很快就会发现不同版本的 msvcrt***.dll

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

    msvcrt***.dll 作为C++库。然后使用 ctypes

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

    “美丽的本地图书馆 (Python语言) ": http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/

        11
  •  3
  •   Kaan E.    5 年前

    我知道这是一个老问题,但当你在谷歌上搜索像 ctypes vs cython ,这里的大部分答案都是由那些已经精通 cython c 赛昂 c/c++

    在过去的两天里,我一直在寻找一种方法,将我代码中性能较高的部分委托给比python更低级的东西。我在两个方面都实现了我的代码 ctypes Cython ,它基本上由两个简单的函数组成。

    字符串列表 这需要处理。通知 list string . 这两种类型并不完全对应于中的类型 C ,因为python字符串默认为unicode和 C

    这是我的判决。使用 . 它与python的集成更加流畅,并且通常更易于使用。当出现问题时 至少,这是你的错 赛昂 将在可能的情况下使用堆栈跟踪向您提供编译警告,并且您可以使用 .

    • C类型:

      • SO 一旦我写了函数。
      • 大约半小时用c编写代码,将其编译到动态库中。
      • C
      • 大约一个小时的测试和重新安排 C 代码。
      • C 他跟我玩得不好 multiprocessing 默认情况下,作为其处理程序的模块不可拾取。
      • 大约20分钟后,我重新安排了我的代码,不再使用 多处理 模块,然后重试。
      • 然后是我的第二个函数 C
      • 我将函数拆分为两个库并重试。我的第二个功能仍然存在故障。
      • 我决定放弃第二个函数,只使用 在使用它的python循环的第二次或第三次迭代中,我有一个 UnicodeError 关于不在某个位置解码一个字节,尽管我对每件事都进行了明确的编码和解码。

    赛昂

    • 赛昂
      • 10分钟的阅读 cython hello world .
      • SO 关于如何使用cython和 setuptools 而不是 distutils
      • 10分钟的阅读时间 cython types 和python类型。我了解到我可以使用大多数内置python类型进行静态类型。
      • 用cython类型重新标记python代码15分钟。
      • setup.py 在我的代码库中使用编译模块。
      • 将模块直接插入到 代码库的版本。它起作用了。

    为了记录在案,我当然没有衡量我投资的确切时间。很可能是这样的,我对时间的感知有点过于专注,这是因为我在处理ctypes时需要付出精神上的努力。但它应该传达出处理问题的感觉 赛昂 ctypes

        12
  •  2
  •   plaes    13 年前

    还有一种可能使用 GObject Introspection 对于正在使用 GLib .