打包二进制扩展

页面状态

不完整

上次审阅时间

2013-12-08

c python引用解释器的一个特性是,除了允许执行python代码之外,它还公开了一个富C API供其他软件使用。这个C API最常见的用途之一是创建可导入的C扩展,允许在纯Python代码中不容易实现的东西。

二进制扩展概述

用例

二进制扩展的典型用例分为三类:

  • 加速器模块:这些模块是完全独立的,并且创建它们的目的仅仅是为了比在cpython中运行的等效纯python代码更快地运行。理想情况下,如果加速版本在给定的系统上不可用,加速器模块将始终具有纯python等价物以用作回退。CPython标准库广泛使用加速器模块。

  • 包装模块:创建这些模块是为了向Python代码公开现有的C接口。它们要么直接公开底层的C接口,要么公开一个更“pythonic”的API,该API使用了Python语言特性,使API更易于使用。CPython标准库广泛使用包装模块。

  • 低级系统访问:创建这些模块是为了访问cpython运行时、操作系统或底层硬件的低级功能。通过平台特定的代码,扩展模块可以实现纯Python代码中不可能实现的东西。许多cpython标准库模块都是用C语言编写的,以访问在语言级别不公开的解释器内部。

    C扩展的一个特别显著的特性是,当它们不需要回调解释器运行时时,它们可以围绕长时间运行的操作释放cpython的全局解释器锁(不管这些操作是CPU还是IO绑定的)。

并非所有扩展模块都能整齐地适应上述类别。例如,numpy中包含的扩展模块跨越了所有三个用例——它们出于速度原因将内部循环移动到c,用c、fortran和其他语言包装外部库,并为cpython和底层操作系统使用低级系统接口来支持con。矢量化操作的当前执行,并严格控制所创建对象的精确内存布局。

缺点

使用二进制扩展的主要缺点是它使软件的后续分发更加困难。使用Python的优点之一是它在很大程度上是跨平台的,并且用于编写扩展模块的语言(通常是C或C++,但实际上任何可以绑定到Cpython C API的语言)通常需要为不同的平台创建自定义二进制文件。

这意味着二进制扩展:

  • 要求最终用户能够从源代码构建它们,或者某人为公共平台发布预构建的二进制文件。

  • 可能与cpython引用解释器的不同版本不兼容

  • 通常情况下,与Pypy、Ironpython或Jython等其他解释器一起工作时会出错。

  • 如果是手工编码的,通过要求维护人员不仅熟悉Python,而且熟悉用于创建二进制扩展的语言,以及c python c API的详细信息,使得维护更加困难。

  • 如果提供了纯Python回退实现,则需要在两个地方实现更改,并在测试套件中引入额外的复杂性,以确保始终执行这两个版本,从而使维护更加困难。

依赖二进制扩展的另一个缺点是,替代的导入机制(例如直接从zipfiles导入模块的能力)通常不适用于扩展模块(因为大多数平台上的动态加载机制只能从磁盘加载库)。

手动编码加速器模块的替代方案

当扩展模块只是用来使代码运行更快时(在分析确定了速度增加值得额外维护的代码之后),还应考虑其他一些替代方法:

  • 寻找现有的优化方案。cpython标准libary包括许多优化的数据结构和算法(尤其是内置和 collectionsitertools 模块)。python包索引还提供了其他的选择。有时,适当选择标准库或第三方模块可以避免创建自己的加速器模块。

  • 对于长时间运行的应用程序,JIT编译 PyPy interpreter 可以提供标准cpython运行时的适当替代方案。采用pypy的主要障碍通常是依赖于其他二进制扩展模块,而pypypy确实模拟了cpython c api,而依赖于它的模块会导致pypy jit出现问题,并且仿真层经常会暴露出cpython当前所使用的扩展模块中的潜在缺陷。ates(经常围绕引用计数错误——一个对象有一个活动引用而不是两个,通常不会破坏任何内容,但是没有引用而不是一个是一个主要问题)。

  • Cython 是一个成熟的静态编译器,可以将大多数Python代码编译为C扩展模块。最初的编译提供了一些速度提升(通过绕过cpython解释器层),而cython可选的静态类型特性可以提供额外的速度提升机会。使用Cython仍然具有增加分发所得到的应用程序的复杂性的缺点,但是它有利于减少Python程序员(相对于其他语言如C或C++)进入的障碍。

  • Numba 是一个更新的工具,由科学的python社区成员创建,旨在利用llvm允许在运行时选择性地将python应用程序的片段编译为本机代码。它要求LLVM在运行代码的系统上可用,但可以显著提高速度,特别是对于易于向量化的操作。

手工编码包装模块的替代方案

C ABI(应用程序二进制接口)是在多个应用程序之间共享功能的通用标准。CPythonC API(应用程序编程接口)的一个优点是允许Python用户利用该功能。然而,手工包装模块非常繁琐,因此应考虑其他一些替代方法。

下面描述的方法根本不简化分发情况,但是它们 can 显著减少保持包装模块最新的维护负担。

  • 除了有助于创建加速器模块外, Cython 对于创建包装模块也很有用。但是,它仍然需要手工包装接口,因此对于包装大型API来说,这可能不是一个好的选择。

  • cffi 是一些Pypy开发人员创建的一个项目,目的是使已经了解Python和C的开发人员能够直接将其C模块公开给Python应用程序。它还使基于头文件包装C模块变得相对简单,即使您自己不了解C。

    其主要优点之一是 cffi 它与pypy-jit兼容,允许cffi包装模块完全参与pypy的跟踪jit优化。

  • SWIG 是一个包装接口生成器,它允许各种编程语言(包括python)与C接口 C++ 代码。

  • The standard library's ctypes module, while useful for getting access to C level interfaces when header information isn't available, suffers from the fact that it operates solely at the C ABI level, and thus has no automatic consistency checking between the interface actually being exported by the library and the one declared in the Python code. By contrast, the above alternatives are all able to operate at the C API level, using C header files to ensure consistency between the interface exported by the library being wrapped and the one expected by the Python wrapper module. While cffi can operate directly at the C ABI level, it suffers from the same interface inconsistency problems as ctypes when it is used that way.

低级系统访问的替代方案

对于需要低级别系统访问的应用程序(无论原因如何),二进制扩展模块通常 is 最好的方法。对于对cpython运行时本身的低级访问尤其如此,因为有些操作(比如释放全局解释器锁)在解释器运行代码时是完全无效的,即使类似这样的模块 ctypescffi 用于访问相关的C API接口。

对于扩展模块正在操作底层操作系统或硬件(而不是cPython运行时)的情况,有时编写一个普通的C库(或者在另一个系统编程语言如C++或RIST中的一个库,可以导出兼容C的AB)可能更好。i),然后使用上面描述的一种包装技术,使接口作为可导入的Python模块可用。

实现二进制扩展

塞顿人 Extending and Embedding 指南包括写作入门 custom extension module in C .

mention the stable ABI (3.2+, link to the CPython C API docs)
mention the module lifecycle
mention the challenges of shared static state and subinterpreters
mention the implications of the GIL for extension modules
mention the memory allocation APIs in 3.4+

mention again that all this is one of the reasons why you probably
*don't* want to handcode your extension modules :)

正在生成二进制扩展

Windows的二进制扩展

在可以构建二进制扩展之前,必须确保有合适的编译器可用。在Windows上,VisualC用于构建官方的cpython解释器,并且应该用于构建兼容的二进制扩展。

python 2.7使用Visual Studio 2008,python 3.3和3.4使用Visual Studio 2010,python 3.5+使用Visual Studio 2015或更高版本。不幸的是,旧版本的Visual Studio不再容易从Microsoft获得,因此对于3.5之前的Python版本,如果您没有相关版本的Visual Studio的副本,则必须以不同的方式获取编译器。

要为二进制扩展设置构建环境,步骤如下:

对于python 2.7

  1. 安装“Python 2.7的Visual C++编译包”,可从 Microsoft's website .

  2. 在setup.py中使用(最新版本的)setuptools(在任何情况下,PIP都会为您这样做)。

  3. 完成。

对于python 3.4

  1. 安装“Windows SDK for Windows 7 and.NET Framework 4”(V7.1),可从 Microsoft's website .

  2. 在sdk命令提示下工作(设置环境变量,sdk在path上)。

  3. 设置distutils_use_sdk=1

  4. 完成。

对于python 3.5

  1. 安装 Visual Studio 2015 Community Edition (或任何更高版本,发布时)。

  2. 完成。

请注意,从python 3.5开始,Visual Studio以向后兼容的方式工作,这意味着任何未来版本的Visual Studio都可以为3.5以后的所有python版本构建python扩展。

在Windows上使用推荐的编译器构建可以确保在整个Python过程中使用兼容的C库。

Linux的二进制扩展

Linux二进制文件必须使用足够旧的glibc才能与旧的发行版兼容。这个 manylinux Docker映像为构建环境提供了一个足够旧的glibc,以支持通用体系结构上最新的Linux发行版。

MacOS的二进制扩展

MacOS上的二进制兼容性由目标最小部署系统确定,例如 10.9 ,通常与 MACOSX_DEPLOYMENT_TARGET 在MacOS上构建二进制文件时的环境变量。使用setuptools/distuils生成时,使用标志指定部署目标 --plat-name ,例如 macosx-10.9-x86_64 . 有关macos python发行版的常见部署目标,请参见 MacPython Spinning Wheels wiki .

发布二进制扩展

有关此主题的临时指导,请参阅中的讨论 this issue .

FIXME

cover publishing as wheel files on PyPI or a custom index server
cover creation of Windows and macOS installers
cover weak linking
mention the fact that Linux distros have a requirement to build from
source in their own build systems, so binary-only releases are strongly
discouraged

其他资源

跨平台开发和分发扩展模块是一个复杂的主题,因此本指南主要关注于为自动处理底层技术挑战的各种工具提供指针。本节中的附加资源是为开发人员准备的,这些开发人员希望更多地了解那些系统在运行时所依赖的底层二进制接口。

带SciKit构建的跨平台车轮生成

这个 scikit-build 包帮助抽象跨平台构建操作,并在创建二进制扩展包时提供附加功能。其他文件也可在 C runtime, compiler, and build system generator 用于python二进制扩展模块。

C/C++扩展模块介绍

有关CPython如何在Debian系统上使用扩展模块的更深入的说明,请参阅以下文章: