摘要:您是领有想要从 Python 中应用的C或 C++ 库的 Python 开发人员吗?如果是这样,那么Python 绑定容许您调用函数并将数据从 Python 传递到C或C++,让您利用这两种语言的劣势。
本文分享自华为云社区《Python 绑定:从 Python 调用 C 或 C++ |【成长吧!Python!】》,原文作者:Yuchuan 。
您是领有想要从 Python 中应用的C或 C++ 库的 Python 开发人员吗?如果是这样,那么Python 绑定容许您调用函数并将数据从 Python 传递到C或C++,让您利用这两种语言的劣势。在本教程中,您将看到一些可用于创立 Python 绑定的工具的概述。
在本教程中,您将理解:
- 为什么要从 Python调用 C 或 C++
- 如何在 C 和 Python 之间传递数据
- 哪些工具和办法能够帮忙您创立 Python 绑定
本教程面向中级 Python 开发人员。它假设读者具备 Python 的基本知识,并对 C 或 C++ 中的函数和数据类型有所理解。您能够通过单击上面的链接获取本教程中将看到的所有示例代码:
Python Bindings概述
在深入研究如何从 Python 调用 C之前,最好花一些工夫理解为什么. 有几种状况下,创立 Python 绑定来调用 C 库是一个好主见:
- 您曾经领有一个用 C++ 编写的大型、通过测试的稳固库,您想在 Python 中利用它。这可能是一个通信库或一个与特定硬件对话的库。它做什么并不重要。
- 您心愿通过将要害局部转换为 C来减速 Python 代码的特定局部。 C 不仅具备更快的执行速度,而且还容许您解脱GIL的限度,前提是您小心。
- 您想应用 Python 测试工具对其零碎进行大规模测试。
以上所有都是学习创立 Python 绑定以与 C 库交互的重要起因。
留神:在本教程中,您将创立到 C和C++ 的Python 绑定。大多数通用概念实用于两种语言,因而除非两种语言之间存在特定差别,否则将应用 C。通常,每个工具都反对 C或C++,但不能同时反对两者。
让咱们开始吧!
编组数据类型
期待!在开始编写 Python 绑定之前,先看看 Python 和 C 如何存储数据以及这会导致哪些类型的问题。首先,让咱们定义编组。这个概念由维基百科定义如下:
将对象的内存示意转换为适宜存储或传输的数据格式的过程。
出于您的目标,编组是 Python 绑定在筹备数据以将其从 Python 挪动到 C 或反之亦然时所做的工作。Python 绑定须要进行编组,因为 Python 和 C 以不同的形式存储数据。C 在内存中以最紧凑的模式存储数据。如果您应用uint8_t,那么它总共将只应用 8 位内存。
另一方面,在 Python 中,一切都是对象。这意味着每个整数在内存中应用几个字节。多少取决于您运行的 Python 版本、操作系统和其余因素。这意味着您的 Python 绑定将须要为每个跨边界传递的整数将C 整数转换为Python 整数。
其余数据类型在这两种语言之间具备类似的关系。让咱们顺次来看看:
- 整数存储计数数字。Python 以任意精度存储整数,这意味着您能够存储十分十分大的数字。C 指定整数的确切大小。在语言之间挪动时须要留神数据大小,以避免 Python 整数值溢出 C 整数变量。
- 浮点数是带有小数位的数字。Python 能够存储比 C 大得多(和小得多)的浮点数。这意味着您还必须留神这些值以确保它们放弃在范畴内。
- 复数是带有虚部的数字。尽管 Python 具备内置复数,而 C 具备复数,但没有用于在它们之间编组的内置办法。要封送复数,您须要在 C 代码中构建struct或class来治理它们。
- 字符串是字符序列。作为这样一种常见的数据类型,当您创立 Python 绑定时,字符串将被证实是相当辣手的。与其余数据类型一样,Python 和 C 以齐全不同的格局存储字符串。(与其余数据类型不同,这也是 C 和 C++ 不同的畛域,这减少了乐趣!)您将钻研的每个解决方案都有稍微不同的解决字符串的办法。
- 布尔变量只能有两个值。因为它们在 C 中失去反对,因而将它们编组将被证实是相当简略的。
除了数据类型转换之外,在构建 Python 绑定时还须要思考其余问题。让咱们持续摸索它们。
理解可变和不可变值
除了所有这些数据类型之外,您还必须理解 Python 对象如何可变或不可变。当谈到传值或传援用时,C 有一个相似的函数参数概念。在 C 中,所有参数都是按值传递的。如果要容许函数更改调用方中的变量,则须要传递指向该变量的指针。
您可能想晓得是否能够通过应用指针将不可变对象简略地传递给 C 来绕过不可变限度。除非你走到俊俏和不可移植的极其,否则Python 不会给你一个指向 object 的指针,所以这行不通。如果您想用 C 批改 Python 对象,那么您须要采取额定的步骤来实现这一点。这些步骤将取决于您应用的工具,如下所示。
因而,您能够将不变性增加到您创立 Python 绑定时要思考的我的项目清单中。在创立此清单的雄伟之旅中,您的最初一站是如何解决 Python 和 C 解决内存治理的不同形式。
治理内存
C 和 Python治理内存的形式不同。在 C 中,开发人员必须治理所有内存调配并确保它们被开释一次且仅一次。Python 应用垃圾收集器为您解决这个问题。
尽管这些办法中的每一种都有其长处,但它的确为创立 Python 绑定增加了额定的麻烦。您须要晓得每个对象的内存调配在哪里,并确保它只在语言障碍的同一侧被开释。
例如,当您设置x = 3. 用于此的内存在 Python 端调配,须要进行垃圾收集。侥幸的是,应用 Python 对象,很难做任何其余事件。看看 C 中的逆向,间接调配一块内存:
int* iPtr = (int*)malloc(sizeof(int));
执行此操作时,您须要确保在 C 中开释此指针。这可能意味着手动将代码增加到 Python 绑定中以执行此操作。
这欠缺了您的个别主题清单。让咱们开始设置您的零碎,以便您能够编写一些代码!
设置您的环境
在本教程中,您将应用来自 Real Python GitHub 存储库的事后存在的 C 和 C++ 库来展现每个工具的测试。目标是您将可能将这些想法用于任何 C 库。要遵循此处的所有示例,您须要具备以下条件:
- 装置的C++ 库和命令行调用门路的常识
- Python开发工具:
- 对于 Linux,这是python3-dev或python3-devel包,具体取决于您的发行版。
- 对于 Windows,有多个选项。
- Python 3.6或更高版本
- 一个虚拟环境(倡议,但不要求)
- 该invoke工具
最初一个对你来说可能是新的,所以让咱们认真看看它。
应用invoke工具
invoke是您将在本教程中用于构建和测试 Python 绑定的工具。它具备相似的目标,make但应用 Python 而不是 Makefiles。您须要invoke应用pip以下命令在虚拟环境中装置:
$ python3 -m pip install invoke
要运行它,请键入invoke后跟要执行的工作:
$ invoke build-cmult=================================================== Building C Library* Complete
要查看哪些工作可用,请应用以下--list选项:
$ invoke --listAvailable tasks: all Build and run all tests build-cffi Build the CFFI Python bindings build-cmult Build the shared library for the sample C code build-cppmult Build the shared library for the sample C++ code build-cython Build the cython extension module build-pybind11 Build the pybind11 wrapper library clean Remove any built objects test-cffi Run the script to test CFFI test-ctypes Run the script to test ctypes test-cython Run the script to test Cython test-pybind11 Run the script to test PyBind11
请留神,当您查看定义工作的tasks.py文件时invoke,您会看到列出的第二个工作的名称是build_cffi. 然而,来自的输入将其--list显示为build-cffi. 减号 ( -) 不能用作 Python 名称的一部分,因而该文件应用下划线 ( _) 代替。
对于您将查看的每个工具,都会定义一个build-和一个test-工作。例如,要运行 的代码CFFI,您能够键入invoke build-cffi test-cffi。一个例外是ctypes,因为 没有构建阶段ctypes。此外,为了不便,还增加了两个特殊任务:
- invoke all 运行所有工具的构建和测试工作。
- invoke clean 删除任何生成的文件。
既然您曾经对如何运行代码有所理解,那么在查看工具概述之前,让咱们先看一下您将要包装的 C 代码。
C 或 C++ 源代码
在上面的每个示例局部中,您将为C 或 C++ 中的雷同函数创立 Python 绑定。这些局部旨在让您体验每种办法的外观,而不是无关该工具的深刻教程,因而您将封装的函数很小。您将为其创立 Python 绑定的函数将 anint和 afloat作为输出参数并返回一个float是两个数字的乘积:
// cmult.cfloat cmult(int int_param, float float_param) { float return_value = int_param * float_param; printf(" In cmult : int: %d float %.1f returning %.1f\n", int_param, float_param, return_value); return return_value;}
C 和 C++ 函数简直雷同,它们之间的名称和字符串略有不同。您能够通过单击以下链接获取所有代码的正本:
当初您曾经克隆了 repo 并装置了工具,您能够构建和测试这些工具。因而,让咱们深刻理解上面的每个局部!
ctypes
您将从 开始ctypes,它是规范库中用于创立 Python 绑定的工具。它提供了一个低级工具集,用于在 Python 和 C 之间加载共享库和编组数据。
它是如何装置的
的一大长处ctypes是它是 Python 规范库的一部分。它是在 Python 2.5 版中增加的,因而您很可能曾经领有它。您能够import像应用sys或time模块一样。
调用函数
加载 C 库和调用函数的所有代码都将在 Python 程序中。这很棒,因为您的过程中没有额定的步骤。您只需运行您的程序,所有都会失去解决。要在 中创立 Python 绑定ctypes,您须要执行以下步骤:
- 加载您的库。
- 包装一些输出参数。
- 通知 ctypes你函数的返回类型。
您将顺次查看其中的每一个。
库加载
ctypes为您提供了多种加载共享库的办法,其中一些是特定于平台的。对于您的示例,您将ctypes.CDLL通过传入所需共享库的残缺门路来间接创建对象:
# ctypes_test.pyimport ctypesimport pathlibif __name__ == "__main__": # Load the shared library into ctypes libname = pathlib.Path().absolute() / "libcmult.so" c_lib = ctypes.CDLL(libname)
这实用于共享库与 Python 脚本位于同一目录中的状况,但在尝试加载来自 Python 绑定以外的包的库时要小心。在ctypes特定于平台和特定状况的文档中,有许多对于加载库和查找门路的详细信息。
留神:在库加载过程中可能会呈现许多特定于平台的问题。最好在示例工作后进行增量更改。
当初您已将库加载到 Python 中,您能够尝试调用它!
调用你的函数
请记住,您的 C 函数的函数原型如下:
// cmult.hfloat cmult(int int_param, float float_param);
您须要传入一个整数和一个浮点数,并且能够冀望失去一个浮点数返回。整数和浮点数在 Python 和 C 中都有本机反对,因而您心愿这种状况实用于正当的值。
将库加载到 Python 绑定中后,该函数将成为 的属性c_lib,即CDLL您之前创立的对象。您能够尝试这样称说它:
x, y = 6, 2.3answer = c_lib.cmult(x, y)
哎呀!这不起作用。此行在示例 repo 中被正文掉,因为它失败了。如果您尝试应用该调用运行,那么 Python 会报错:
$ invoke test-ctypesTraceback (most recent call last): File "ctypes_test.py", line 16, in <module> answer = c_lib.cmult(x, y)ctypes.ArgumentError: argument 2: <class 'TypeError'>: Don't know how to convert parameter 2
看起来您须要阐明ctypes任何不是整数的参数。ctypes除非您明确通知它,否则您对该函数无所不知。任何未以其余形式标记的参数都假设为整数。ctypes不晓得如何将2.3存储的值转换为y整数,所以它失败了。
要解决此问题,您须要c_float从号码中创立一个。您能够在调用函数的行中执行此操作:
# ctypes_test.pyanswer = c_lib.cmult(x, ctypes.c_float(y))print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")
当初,当您运行此代码时,它会返回您传入的两个数字的乘积:
$ invoke test-ctypes In cmult : int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 48.0
等一下……6乘以2.3不是48.0!
事实证明,就像输出参数一样,ctypes 假如您的函数返回一个int. 实际上,您的函数返回 a float,它被谬误地编组。就像输出参数一样,您须要通知ctypes应用不同的类型。这里的语法略有不同:
# ctypes_test.pyc_lib.cmult.restype = ctypes.c_floatanswer = c_lib.cmult(x, ctypes.c_float(y))print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")
这应该够了吧。让咱们运行整个test-ctypes指标,看看你有什么。请记住,输入的第一局部是在restype将函数固定为浮点数之前:
$ invoke test-ctypes=================================================== Building C Library* Complete=================================================== Testing ctypes Module In cmult : int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 48.0 In cmult : int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 13.8
这样更好!尽管第一个未更正的版本返回谬误的值,但您的固定版本与 C 函数统一。C 和 Python 都失去雷同的后果!当初它能够工作了,看看为什么您可能想或不想应用ctypes.
短处和短处
ctypes与您将在此处查看的其余工具相比,最大的劣势在于它内置于规范库中。它还不须要额定的步骤,因为所有工作都是作为 Python 程序的一部分实现的。
此外,所应用的概念是低级的,这使得像您刚刚做的那样的练习易于治理。然而,因为不足自动化,更简单的工作变得繁琐。在下一部分中,您将看到一个工具,该工具为流程增加了一些自动化。
CFFI
CFFI是Python的C 外来函数接口。生成 Python 绑定须要更自动化的办法。CFFI有多种形式能够构建和应用 Python 绑定。有两种不同的选项可供选择,为您提供四种可能的模式:
- ABI vs API: API 模式应用 C 编译器生成残缺的 Python 模块,而 ABI 模式加载共享库并间接与其交互。在不运行编译器的状况下,获取正确的构造和参数很容易出错。该文档强烈建议应用 API 模式。
内联 vs 外联:这两种模式的区别在于速度和便利性之间的衡量:
- 每次运行脚本时,内联模式都会编译 Python 绑定。这很不便,因为您不须要额定的构建步骤。然而,它的确会减慢您的程序速度。
- Out-of-line 模式须要一个额定的步骤来一次性生成 Python 绑定,而后在每次程序运行时应用它们。这要快得多,但这对您的应用程序可能无关紧要。
对于此示例,您将应用 API 外联模式,它生成最快的代码,并且通常看起来相似于您将在本教程前面创立的其余 Python 绑定。
它是如何装置的
因为CFFI不是规范库的一部分,您须要在您的机器上装置它。建议您为此创立一个虚拟环境。侥幸的是,CFFI装置有pip:
$ python3 -m pip install cffi
这会将软件包装置到您的虚拟环境中。如果您曾经从 装置requirements.txt,那么应该留神这一点。您能够requirements.txt通过拜访以下链接中的 repo 来查看:
获取示例代码: 单击此处获取您将用于在本教程中理解 Python 绑定的示例代码。
当初你曾经CFFI装置好了,是时候试一试了!
调用函数
与 不同的是ctypes,CFFI您正在创立一个残缺的 Python 模块。您将可能import像规范库中的任何其余模块一样应用该模块。您须要做一些额定的工作来构建 Python 模块。要应用CFFIPython 绑定,您须要执行以下步骤:
- 编写一些形容绑定的 Python 代码。
- 运行该代码以生成可加载模块。
- 批改调用代码以导入和应用新创建的模块。
这可能看起来须要做很多工作,但您将实现这些步骤中的每一个,并理解它是如何工作的。
编写绑定
CFFI提供读取C 头文件的办法,以在生成 Python 绑定时实现大部分工作。在 的文档中CFFI,执行此操作的代码搁置在独自的 Python 文件中。对于此示例,您将间接将该代码放入构建工具中invoke,该工具应用 Python 文件作为输出。要应用CFFI,您首先要创立一个cffi.FFI对象,该对象提供了您须要的三种办法:
# tasks.pyimport cffi...""" Build the CFFI Python bindings """print_banner("Building CFFI Module")ffi = cffi.FFI()
领有 FFI 后,您将应用.cdef()来主动解决头文件的内容。这会为您创立包装函数以从 Python 封送数据:
# tasks.pythis_dir = pathlib.Path().absolute()h_file_name = this_dir / "cmult.h"with open(h_file_name) as h_file: ffi.cdef(h_file.read())
读取和解决头文件是第一步。之后,您须要应用.set_source()来形容CFFI将生成的源文件:
# tasks.pyffi.set_source( "cffi_example", # Since you're calling a fully-built library directly, no custom source # is necessary. You need to include the .h files, though, because behind # the scenes cffi generates a .c file that contains a Python-friendly # wrapper around each of the functions. '#include "cmult.h"', # The important thing is to include the pre-built lib in the list of # libraries you're linking against: libraries=["cmult"], library_dirs=[this_dir.as_posix()], extra_link_args=["-Wl,-rpath,."],)
以下是您传入的参数的细分:
- "cffi_example"是将在您的文件系统上创立的源文件的根本名称。CFFI将生成一个.c文件,将其编译为一个.o文件,并将其链接到一个.<system-description>.so或.<system-description>.dll文件。
- '#include "cmult.h"'是自定义 C 源代码,它将在编译之前蕴含在生成的源代码中。在这里,您只需蕴含.h要为其生成绑定的文件,但这可用于一些乏味的自定义。
- libraries=["cmult"]通知链接器您事后存在的 C 库的名称。这是一个列表,因而您能够依据须要指定多个库。
- library_dirs=[this_dir.as_posix(),] 是一个目录列表,通知链接器在何处查找上述库列表。
- extra_link_args=['-Wl,-rpath,.']是一组生成共享对象的选项,它将在以后门路 ( .) 中查找它须要加载的其余库。
构建 Python 绑定
调用.set_source()不会构建 Python 绑定。它只设置元数据来形容将生成的内容。要构建 Python 绑定,您须要调用.compile():
# tasks.pyffi.compile()
这通过生成.c文件、.o文件和共享库来实现。在invoke你刚走通过工作能够在上运行命令行构建Python绑定:
$ invoke build-cffi=================================================== Building C Library* Complete=================================================== Building CFFI Module* Complete
你有你的CFFIPython 绑定,所以是时候运行这段代码了!
调用你的函数
在您为配置和运行CFFI编译器所做的所有工作之后,应用生成的 Python 绑定看起来就像应用任何其余 Python 模块一样:
# cffi_test.pyimport cffi_exampleif __name__ == "__main__": # Sample data for your call x, y = 6, 2.3 answer = cffi_example.lib.cmult(x, y) print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")
你导入新模块,而后就能够cmult()间接调用了。要对其进行测试,请应用以下test-cffi工作:
$ invoke test-cffi=================================================== Testing CFFI Module In cmult : int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 13.8
这将运行您的cffi_test.py程序,该程序会测试您应用CFFI. 对于编写和应用CFFIPython 绑定的局部到此结束。
短处和短处
ctypes与CFFI您刚刚看到的示例相比,这仿佛须要更少的工作。尽管这对于这个用例来说是正确的,CFFI但与ctypes因为大部分性能包装的自动化相比,它能够更好地扩大到更大的我的项目。
CFFI也产生了齐全不同的用户体验。ctypes容许您将事后存在的 C 库间接加载到您的 Python 程序中。CFFI,另一方面,创立一个能够像其余 Python 模块一样加载的新 Python 模块。
更重要的是,应用下面应用的内部 API办法,创立 Python 绑定的工夫损失在您构建它时实现一次,并且不会在每次运行代码时产生。对于小程序来说,这可能不是什么大问题,但也能够通过CFFI这种形式更好地扩大到更大的我的项目。
就像ctypes, usingCFFI只容许您间接与 C 库交互。C++ 库须要大量的工作能力应用。在下一节中,您将看到一个专一于 C++ 的 Python 绑定工具。
PyBind11
PyBind11应用齐全不同的办法来创立 Python 绑定。除了将重点从 C 转移到 C++ 之外,它还应用 C++ 来指定和构建模块,使其可能利用 C++ 中的元编程工具。像 一样CFFI,生成的 Python 绑定PyBind11是一个残缺的 Python 模块,能够间接导入和应用。
PyBind11以Boost::Python库为底本并具备相似的界面。然而,它将其应用限度为 C++11 和更新版本,与反对所有内容的 Boost 相比,这使其可能简化和放慢处理速度。
它是如何装置的
文档的“第一步”局部将PyBind11疏导您理解如何下载和构建PyBind11. 尽管这仿佛不是严格要求,但实现这些步骤将确保您设置了正确的 C++ 和 Python 工具。
注:大部分示例PyBind11应用cmake,是构建 C 和 C++ 我的项目的好工具。然而,对于此演示,您将持续应用该invoke工具,该工具遵循文档的手动构建局部中的阐明。
您须要将此工具装置到您的虚拟环境中:
$ python3 -m pip install pybind11
PyBind11是一个全头库,相似于 Boost 的大部分内容。这容许pip将库的理论 C++ 源代码间接装置到您的虚拟环境中。
调用函数
在您深入研究之前,请留神您应用的是不同的 C++ 源文件, cppmult.cpp,而不是您用于后面示例的 C 文件。两种语言的性能基本相同。
编写绑定
与 相似CFFI,您须要创立一些代码来通知该工具如何构建您的 Python 绑定。与 不同CFFI,此代码将应用 C++ 而不是 Python。侥幸的是,只须要很少的代码:
// pybind11_wrapper.cpp#include <pybind11/pybind11.h>#include <cppmult.hpp>PYBIND11_MODULE(pybind11_example, m) { m.doc() = "pybind11 example plugin"; // Optional module docstring m.def("cpp_function", &cppmult, "A function that multiplies two numbers");}
让咱们一次一个地看,因为PyBind11将大量信息打包成几行。
前两行包含pybind11.hC++ 库的文件和头文件cppmult.hpp. 之后,你就有了PYBIND11_MODULE宏。这将扩大为PyBind11源代码中详细描述的 C++ 代码块:
此宏创立入口点,当 Python 解释器导入扩大模块时将调用该入口点。模块名称作为第一个参数给出,不利用引号引起来。第二个宏参数定义了一个py::module可用于初始化模块的类型变量。(起源)
这对您来说意味着,在本例中,您正在创立一个名为的模块pybind11_example,其余代码将m用作py::module对象的名称。在下一行,在您定义的 C++ 函数中,您为模块创立一个文档字符串。尽管这是可选的,但让您的模块更加Pythonic是一个不错的抉择。
最初,你有m.def()电话。这将定义一个由您的新 Python 绑定导出的函数,这意味着它将在 Python 中可见。在此示例中,您将传递三个参数:
- cpp_function是您将在 Python 中应用的函数的导出名称。如本例所示,它不须要匹配 C++ 函数的名称。
- &cppmult 获取要导出的函数的地址。
- "A function..." 是函数的可选文档字符串。
当初您曾经有了 Python 绑定的代码,接下来看看如何将其构建到 Python 模块中。
构建 Python 绑定
用于构建 Python 绑定的工具PyBind11是 C++ 编译器自身。您可能须要批改编译器和操作系统的默认值。
首先,您必须构建要为其创立绑定的 C++ 库。对于这么小的示例,您能够将cppmult库间接构建到 Python 绑定库中。然而,对于大多数理论示例,您将有一个要包装的事后存在的库,因而您将cppmult独自构建该库。构建是对编译器的规范调用以构建共享库:
# tasks.pyinvoke.run( "g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC cppmult.cpp " "-o libcppmult.so ")
运行这个invoke build-cppmult产生libcppmult.so:
$ invoke build-cppmult=================================================== Building C++ Library* Complete
另一方面,Python 绑定的构建须要一些非凡的细节:
1# tasks.py 2invoke.run( 3 "g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC " 4 "`python3 -m pybind11 --includes` " 5 "-I /usr/include/python3.7 -I . " 6 "{0} " 7 "-o {1}`python3.7-config --extension-suffix` " 8 "-L. -lcppmult -Wl,-rpath,.".format(cpp_name, extension_name) 9)
让咱们逐行浏览一下。第 3 行蕴含相当规范的 C++ 编译器标记,批示几个细节,包含您心愿捕捉所有正告并将其视为谬误、您须要共享库以及您应用的是 C++11。
第 4 行是魔法的第一步。它调用pybind11模块使其include为PyBind11. 您能够间接在管制台上运行此命令以查看它的作用:
$ python3 -m pybind11 --includes-I/home/jima/.virtualenvs/realpython/include/python3.7m-I/home/jima/.virtualenvs/realpython/include/site/python3.7
您的输入应该类似但显示不同的门路。
在编译调用的第 5 行,您能够看到您还增加了 Python dev 的门路includes。尽管建议您不要链接 Python 库自身,但源代码须要一些代码Python.h能力施展其魔力。侥幸的是,它应用的代码在 Python 版本中相当稳固。
第 5 行还用于-I .将当前目录增加到include门路列表中。这容许#include <cppmult.hpp>解析包装器代码中的行。
第 6 行指定源文件的名称,即pybind11_wrapper.cpp. 而后,在第 7 行,您会看到更多的构建魔法正在产生。此行指定输入文件的名称。Python 在模块命名上有一些特地的想法,包含 Python 版本、机器架构和其余细节。Python 还提供了一个工具来帮忙解决这个问题python3.7-config:
$ python3.7-config --extension-suffix.cpython-37m-x86_64-linux-gnu.so
如果您应用的是不同版本的 Python,则可能须要批改该命令。如果您应用不同版本的 Python 或在不同的操作系统上,您的后果可能会发生变化。
构建命令的最初一行,第 8 行,将链接器指向libcppmult您之前构建的库。该rpath局部通知链接器向共享库增加信息以帮忙操作系统libcppmult在运行时查找。最初,您会留神到此字符串的格局为cpp_name和extension_name。Cython在下一节中构建 Python 绑定模块时,您将再次应用此函数。
运行此命令以构建绑定:
$ invoke build-pybind11=================================================== Building C++ Library* Complete=================================================== Building PyBind11 Module* Complete
就是这样!您曾经应用PyBind11. 是时候测试一下了!
调用你的函数
与CFFI下面的示例相似,一旦您实现了创立 Python 绑定的沉重工作,调用您的函数看起来就像一般的 Python 代码:
# pybind11_test.pyimport pybind11_exampleif __name__ == "__main__": # Sample data for your call x, y = 6, 2.3 answer = pybind11_example.cpp_function(x, y) print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")
因为您pybind11_example在PYBIND11_MODULE宏中用作模块的名称,因而这就是您导入的名称。在m.def()您通知PyBind11将cppmult函数导出为 的调用中cpp_function,这就是您用来从 Python 调用它的办法。
你也能够测试它invoke:
$ invoke test-pybind11=================================================== Testing PyBind11 Module In cppmul: int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 13.8
这就是PyBind11看起来的样子。接下来,您将理解何时以及为何PyBind11是适宜该工作的工具。
短处和短处
PyBind11专一于 C++ 而不是 C,这使得它不同于ctypes和CFFI。它有几个个性使其对 C++ 库十分有吸引力:
- 它反对类。
- 它解决多态子类化。
- 它容许您从 Python 和许多其余工具向对象增加动静属性,而应用您查看过的基于 C 的工具很难做到这一点。
话虽如此,您须要进行大量设置和配置能力PyBind11启动和运行。正确装置和构建可能有点挑剔,但一旦实现,它仿佛相当牢靠。此外,PyBind11要求您至多应用 C++11 或更高版本。对于大多数我的项目来说,这不太可能是一个很大的限度,但它可能是您的一个思考因素。
最初,创立 Python 绑定须要编写的额定代码是用 C++ 编写的,而不是用 Python 编写的。这可能是也可能不是你的问题,但它是比你在这里看到的其余工具不同。在下一节中,您将持续探讨Cython,它采纳齐全不同的办法来解决这个问题。
Cython
该办法Cython须要创立Python绑定应用类Python语言来定义绑定,而后生成的C或C ++代码可被编译成模块。有几种办法能够应用Cython. 最常见的一种是应用setupfrom distutils。对于此示例,您将保持应用该invoke工具,它容许您应用运行的确切命令。
它是如何装置的
Cython是一个 Python 模块,能够从PyPI装置到您的虚拟环境中:
$ python3 -m pip install cython
同样,如果您已将该requirements.txt文件装置到虚拟环境中,则该文件曾经存在。您能够requirements.txt通过单击以下链接获取正本:
获取示例代码: 单击此处获取您将用于在本教程中理解 Python 绑定的示例代码。
这应该让你筹备好与之单干Cython!
调用函数
要应用 构建 Python 绑定Cython,您将遵循与用于CFFI和 的步骤相似的步骤PyBind11。您将编写绑定、构建它们,而后运行 Python 代码来调用它们。Cython能够同时反对 C 和 C++。对于本示例,您将应用cppmult您在PyBind11下面的示例中应用的库。
编写绑定
申明模块的最常见模式Cython是应用.pyx文件:
1# cython_example.pyx 2""" Example cython interface definition """ 3 4cdef extern from "cppmult.hpp": 5 float cppmult(int int_param, float float_param) 6 7def pymult( int_param, float_param ): 8 return cppmult( int_param, float_param )
这里有两个局部:
- 线3和4告诉Cython您应用的是cppmult()从cppmult.hpp。
- 第 6 行和第 7 行创立了一个包装函数pymult(),以调用cppmult()。
这里应用的语言是 C、C++ 和 Python 的非凡组合。不过,对于 Python 开发人员来说,它看起来相当相熟,因为其指标是使过程更容易。
第一局部 withcdef extern...通知Cython上面的函数申明也能够在cppmult.hpp文件中找到。这对于确保依据与 C++ 代码雷同的申明构建 Python 绑定十分有用。第二局部看起来像一个一般的 Python 函数——因为它是!本节创立一个能够拜访 C++ 函数的 Python 函数cppmult。
当初您曾经定义了 Python 绑定,是时候构建它们了!
构建 Python 绑定
构建过程Cython与您应用的构建过程类似PyBind11。您首先Cython在.pyx文件上运行以生成.cpp文件。实现此操作后,您能够应用用于以下内容的雷同函数对其进行编译PyBind11:
1# tasks.py 2def compile_python_module(cpp_name, extension_name): 3 invoke.run( 4 "g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC " 5 "`python3 -m pybind11 --includes` " 6 "-I /usr/include/python3.7 -I . " 7 "{0} " 8 "-o {1}`python3.7-config --extension-suffix` " 9 "-L. -lcppmult -Wl,-rpath,.".format(cpp_name, extension_name)10 )1112def build_cython(c):13 """ Build the cython extension module """14 print_banner("Building Cython Module")15 # Run cython on the pyx file to create a .cpp file16 invoke.run("cython --cplus -3 cython_example.pyx -o cython_wrapper.cpp")1718 # Compile and link the cython wrapper library19 compile_python_module("cython_wrapper.cpp", "cython_example")20 print("* Complete")
您首先运行cython您的.pyx文件。您能够在此命令上应用几个选项:
- --cplus 通知编译器生成 C++ 文件而不是 C 文件。
- -3切换Cython到生成 Python 3 语法而不是 Python 2。
- -o cython_wrapper.cpp 指定要生成的文件的名称。
生成 C++ 文件后,您能够应用 C++ 编译器生成 Python 绑定,就像您为PyBind11. 请留神,include应用该pybind11工具生成额定门路的调用仍在该函数中。在这里不会有任何挫伤,因为您的起源不须要这些。
在 中运行此工作invoke会产生以下输入:
$ invoke build-cython=================================================== Building C++ Library* Complete=================================================== Building Cython Module* Complete
能够看到它构建了cppmult库,而后构建了cython模块来包装它。当初你有了CythonPython 绑定。(试着说的是迅速...)它的工夫来测试一下吧!
调用你的函数
调用新 Python 绑定的 Python 代码与用于测试其余模块的代码十分类似:
1# cython_test.py 2import cython_example 3 4# Sample data for your call 5x, y = 6, 2.3 6 7answer = cython_example.pymult(x, y) 8print(f" In Python: int: {x} float {y:.1f} return val {answer:.1f}")
第 2 行导入新的 Python 绑定模块,并pymult()在第 7 行调用。请记住,该.pyx文件提供了一个 Python 包装器cppmult()并将其重命名为pymult. 应用 invoke 运行您的测试会产生以下后果:
$ invoke test-cython=================================================== Testing Cython Module In cppmul: int: 6 float 2.3 returning 13.8 In Python: int: 6 float 2.3 return val 13.8
你失去和以前一样的后果!
短处和短处
Cython是一个绝对简单的工具,能够在为 C 或 C++ 创立 Python 绑定时为您提供更深层次的管制。尽管您没有在此处深刻介绍它,但它提供了一种 Python 式的办法来编写手动管制GIL 的代码,这能够显着放慢某些类型的问题的处理速度。
然而,这种 Python 格调的语言并不齐全是 Python,因而当您要疾速确定 C 和 Python 的哪些局部适宜何处时,会有一个轻微的学习曲线。
其余解决方案
在钻研本教程时,我遇到了几种用于创立 Python 绑定的不同工具和选项。尽管我将此概述限度为一些更常见的选项,但我偶尔发现了其余几种工具。上面的列表并不全面。如果上述工具之一不适宜您的我的项目,这只是其余可能性的一个示例。
PyBindGen
PyBindGen为 C 或 C++ 生成 Python 绑定并用 Python 编写。它旨在生成可读的 C 或 C++ 代码,这应该能够简化调试问题。目前尚不分明这是否最近已更新,因为文档将 Python 3.4 列为最新的测试版本。然而,在过来的几年里,每年都有公布。
Boost.Python
Boost.Python有一个相似于PyBind11您在下面看到的界面。这不是偶合,因为PyBind11它基于这个库!Boost.Python是用残缺的 C++ 编写的,并且在大多数平台上反对大多数(如果不是全副)C++ 版本。相比之下,PyBind11仅限于古代 C++。
SIP
SIP是为PyQt我的项目开发的用于生成 Python 绑定的工具集。wxPython我的项目也应用它来生成它们的绑定。它有一个代码生成工具和一个额定的 Python 模块,为生成的代码提供反对性能。
Cppyy
cppyy是一个乏味的工具,它的设计指标与您目前所见略有不同。用包作者的话来说:
“cppyy 背地的最后想法(追溯到 2001 年)是容许生存在 C++ 世界中的 Python 程序员拜访那些 C++ 包,而不用间接接触 C++(或期待 C++ 开发人员过去并提供绑定) 。” (起源)
Shiboken
Shiboken是为与 Qt 我的项目关联的 PySide 我的项目开发的用于生成 Python 绑定的工具。尽管它被设计为该项目标工具,但文档表明它既不是 Qt 也不是 PySide 特定的,可用于其余我的项目。
SWIG
SWIG是与此处列出的任何其余工具不同的工具。它是一个通用工具,用于为许多其余语言(而不仅仅是 Python)创立到 C 和 C++ 程序的绑定。这种为不同语言生成绑定的能力在某些我的项目中十分有用。当然,就复杂性而言,它会带来老本。
论断
祝贺!您当初曾经大抵理解了用于创立Python 绑定的几个不同选项。您曾经理解了编组数据以及创立绑定时须要思考的问题。您曾经理解了如何应用以下工具从 Python 调用 C 或 C++ 函数:
- ctypes
- CFFI
- PyBind11
- Cython
您当初晓得,尽管ctypes容许您间接加载 DLL 或共享库,但其余三个工具须要额定的步骤,但仍会创立残缺的 Python 模块。作为处分,您还应用了invoke从 Python 运行命令行工作的工具。
点击关注,第一工夫理解华为云陈腐技术~