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

2025-03-11 08:52:00
admin
原创
31
摘要:问题描述:我想从 Python 应用程序调用 C 库。我不想包装整个 API,只想包装与我的情况相关的函数和数据类型。在我看来,我有三个选择:用 C 创建一个实际的扩展模块。可能有点过度,而且我也想避免学习扩展编写的开销。使用Cython将 C 库中的相关部分暴露给 Python。用 Python 完成整个操...

问题描述:

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

  1. 用 C 创建一个实际的扩展模块。可能有点过度,而且我也想避免学习扩展编写的开销。

  2. 使用Cython将 C 库中的相关部分暴露给 Python。

  3. 用 Python 完成整个操作,用于ctypes与外部库进行通信。

我不确定 2) 还是 3) 是更好的选择。3) 的优点是它ctypes是标准库的一部分,并且生成的代码将是纯 Python 的——尽管我不确定这个优势实际上有多大。

这两种选择各有优缺点吗?您推荐哪种方法?


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

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

再次感谢。


解决方案 1:

警告:前方是 Cython 核心开发人员的观点。

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

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

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

解决方案 2:

ctypes是您快速完成任务的最佳选择,很高兴与您合作,因为您仍在编写 Python!

我最近使用 ctypes 封装了一个用于与 USB 芯片通信的FTDI驱动程序,效果非常好。我在不到一个工作日的时间内就完成了所有工作。(我只实现了我们需要的函数,大约 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。

解决方案 3:

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

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

无论如何,由于 Cython 非常容易安装和使用,我建议你尝试一下,看看它是否适合你的需求。如果它不是你想要的工具,那也不算浪费。

解决方案 4:

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

  • 它以一种迷人、干净的方式处理问题(与ctypes相反)

  • 它不需要编写非 Python 代码(如SWIG、Cython等)

解决方案 5:

我再举一个例子:SWIG

它易于学习,可以完成很多正确的事情,并且支持更多语言,因此花在学习它上的时间非常有用。

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

解决方案 6:

就我个人而言,我会用 C 语言编写扩展模块。不要被 Python C 扩展吓到——它们一点也不难写。文档非常清晰且有用。当我第一次用 Python 编写 C 扩展时,我想我花了大约一个小时才弄清楚如何编写——一点也不费时间。

解决方案 7:

如果您已经有一个具有定义 API 的库,我认为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

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

解决方案 8:

当您已经有编译好的库 blob 需要处理(例如 OS 库)时, ctypes非常有用。但是,调用开销很大,因此,如果您要对库进行大量调用,并且无论如何都要编写 C 代码(或至少编译它),我建议您使用cython。这不需要做太多工作,而且使用生成的 pyd 文件会更快、更符合 Python 风格。

我个人倾向于使用 cython 来快速加速 python 代码(循环和整数比较是 cython 特别出色的两个领域),当涉及到更多复杂的代码/包装其他库时,我会转向Boost.Python。 Boost.Python 的设置可能很棘手,但一旦您让它工作起来,它就使得包装 C/C++ 代码变得简单。

cython 还非常适合包装numpy(这是我从SciPy 2009 会议记录中学到的),但我没有使用过 numpy,所以我无法对此发表评论。

解决方案 9:

我知道这是一个老问题,但是当你在谷歌上搜索类似的东西时,这个问题就会出现ctypes vs cython,而且这里的大多数答案都是由那些已经精通cython或的人写的c,这可能无法反映出你学习这些知识以实施解决方案所需的实际时间。我对这两方面都是完全的初学者。我以前从未接触过cython,而且经验很少c/c++

在过去的两天里,我一直在寻找一种方法,将代码中性能要求较高的部分委托给比 Python 更低级别的程序。我在和中实现了代码ctypesCython它们基本上由两个简单的函数组成。

我有一个巨大的字符串列表需要处理。注意liststring。这两种类型并不完全对应于中的类型c,因为 python 字符串默认为 unicode,而c字符串不是。python 中的列表根本不是 c 数组。

以下是我的结论。使用cython。它与 Python 的集成更流畅,并且总体上更容易使用。当出现问题时ctypes,只会抛出段错误,至少cython会在可能的情况下向您提供带有堆栈跟踪的编译警告,并且您可以使用 轻松返回有效的 Python 对象cython

以下是我为实现相同功能在这两款软件上投入的时间的详细说明。顺便说一下,我很少进行 C/C++ 编程:

  • C类型:

+ 大约花了 2 小时研究如何将我的 unicode 字符串列表转换为 ac 兼容类型。
+ 大约一个小时讲解了如何从 c 函数正确返回字符串。实际上,我在编写函数后提供了自己的SO解决方案。
+ 大约半小时用c编写代码,将其编译为动态库。
+ 花 10 分钟用 python 编写测试代码来检查`c`代码是否有效。
+ 大约花了一个小时进行一些测试并重新整理`c`代码。
+ 然后我将`c`代码插入到实际的代码库中,发现它`ctypes`不能很好地与`multiprocessing`模块配合,因为默认情况下它的处理程序是不可选的。
+ 大约 20 分钟后,我重新安排了我的代码以不使用`multiprocessing`模块,然后重试。
+ 然后,我的代码中的第二个函数`c`在我的代码库中生成了段错误,尽管它通过了我的测试代码。好吧,这可能是我的错,因为我没有很好地检查边缘情况,我正在寻找一个快速的解决方案。
+ 我花了大约 40 分钟的时间尝试确定这些段错误的可能原因。
+ 我将函数拆分成两个库并重试。第二个函数仍然出现段错误。
+ 我决定放弃第二个函数并仅使用`c`代码的第一个函数,并且在使用它的python循环的第二次或第三次迭代中,`UnicodeError`尽管我明确地编码和解码了所有内容,但我却没有在某个位置解码字节。

此时,我决定寻找替代方案,并决定研究cython

  • Cython

    • 阅读cython hello world需要 10 分钟。

    • 花费 15 分钟检查SO以了解如何使用 cythonsetuptools代替distutils

    • 10 分钟阅读有关cython 类型和 python 类型的文章。我了解到我可以使用大多数内置 python 类型进行静态类型输入。

    • 用 15 分钟的时间用 cython 类型重新注释我的 python 代码。

    • setup.py修改我的代码库以使用已编译的模块需要 10 分钟。

    • 将模块直接插入到multiprocessing代码库版本中。它可以工作。

需要说明的是,我当然没有衡量投资的确切时间。很可能是因为我在处理 ctypes 时需要太多脑力劳动,所以我对时间的感知有点过于专注。但它应该传达出处理cythonctypes

解决方案 10:

有一个问题导致我使用 ctypes 而不是 cython,而其他答案中没有提及。

使用 ctypes 时,结果完全不依赖于您使用的编译器。您可以使用几乎任何可以编译为本机共享库的语言编写库。哪个系统、哪种语言和哪种编译器并不重要。然而,Cython 受到基础设施的限制。例如,如果您想在 Windows 上使用英特尔编译器,让 cython 工作就更加棘手:您应该向 cython“解释”编译器,用这个确切的编译器重新编译某些东西,等等。这大大限制了可移植性。

解决方案 11:

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

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

然后,为了使一切正常,您需要为 C++ 库创建 C 包装器,将该包装器 dll 链接到msvcrt***.dll与 C++ 库相同的版本。然后ctypes在运行时动态加载您手动编写的包装器 dll。

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

“漂亮的本机库(使用 Python) ”:http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/

解决方案 12:

对于使用GLib 的库,还有一种可能性是使用GObject Introspection。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1989  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1446  
  在当今快速发展的IT行业中,项目管理工具的选择对于项目的成功至关重要。随着技术的不断进步,项目经理们需要更加高效、灵活的工具来应对复杂的项目需求。本文将介绍2025年IT项目经理力推的10款管理工具,帮助您在项目管理中取得更好的成果。信创国产项目管理软件 - 禅道禅道是一款国产开源的项目管理软件,禅道开源版不限人数,功...
项目管理工具   0  
  在当今快速变化的商业环境中,项目管理软件已成为企业提升效率、优化资源分配和确保项目成功的关键工具。随着技术的不断进步,市场上涌现出众多功能各异的项目管理工具,每一款都有其独特的优势和适用场景。本文将深入评测2025年最受欢迎的10款项目管理软件,帮助您根据自身需求做出明智的选择。信创国产项目管理软件 - 禅道禅道是一款...
项目管理平台   2  
  产品开发效率对于企业的竞争力至关重要。在当今复杂多变的商业环境中,如何有效提升产品开发效率成为众多企业关注的焦点。产品生命周期管理(PLM)作为一种整合产品全生命周期信息的管理理念和技术,为提升产品开发效率提供了有力的支持。通过合理运用PLM,企业能够优化流程、加强协作、提高数据管理水平,从而实现产品开发的高效运作。接...
plm开发流程软件   3  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用