Packaging binary extensions - Python Packaging User Guide (original) (raw)

Page Status:

Incomplete

Last Reviewed:

2013-12-08

One of the features of the CPython reference interpreter is that, in addition to allowing the execution of Python code, it also exposes a rich C API for use by other software. One of the most common uses of this C API is to create importable C extensions that allow things which aren’t always easy to achieve in pure Python code.

An overview of binary extensions

Use cases

The typical use cases for binary extensions break down into just three conventional categories:

Not all extension modules will fit neatly into the above categories. The extension modules included with NumPy, for example, span all three use cases - they move inner loops to C for speed reasons, wrap external libraries written in C, FORTRAN and other languages, and use low level system interfaces for both CPython and the underlying operation system to support concurrent execution of vectorised operations and to tightly control the exact memory layout of created objects.

Disadvantages

The main disadvantage of using binary extensions is the fact that it makes subsequent distribution of the software more difficult. One of the advantages of using Python is that it is largely cross platform, and the languages used to write extension modules (typically C or C++, but really any language that can bind to the CPython C API) typically require that custom binaries be created for different platforms.

This means that binary extensions:

Another disadvantage of relying on binary extensions is that alternative import mechanisms (such as the ability to import modules directly from zipfiles) often won’t work for extension modules (as the dynamic loading mechanisms on most platforms can only load libraries from disk).

Alternatives to handcoded accelerator modules

When extension modules are just being used to make code run faster (after profiling has identified the code where the speed increase is worth additional maintenance effort), a number of other alternatives should also be considered:

Alternatives to handcoded wrapper modules

The C ABI (Application Binary Interface) is a common standard for sharing functionality between multiple applications. One of the strengths of the CPython C API (Application Programming Interface) is allowing Python users to tap into that functionality. However, wrapping modules by hand is quite tedious, so a number of other alternative approaches should be considered.

The approaches described below don’t simplify the distribution case at all, but they can significantly reduce the maintenance burden of keeping wrapper modules up to date.

Alternatives for low level system access

For applications that need low level system access (regardless of the reason), a binary extension module often is the best way to go about it. This is particularly true for low level access to the CPython runtime itself, since some operations (like releasing the Global Interpreter Lock) are simply invalid when the interpreter is running code, even if a module like ctypes or cffi is used to obtain access to the relevant C API interfaces.

For cases where the extension module is manipulating the underlying operating system or hardware (rather than the CPython runtime), it may sometimes be better to just write an ordinary C library (or a library in another systems programming language like C++ or Rust that can export a C compatible ABI), and then use one of the wrapping techniques described above to make the interface available as an importable Python module.

Implementing binary extensions

The CPython Extending and Embeddingguide includes an introduction to writing acustom extension module in C.

FIXME: Elaborate that all this is one of the reasons why you probably_don’t_ want to handcode your extension modules :)

Extension module lifecycle

FIXME: This section needs to be fleshed out.

Implications of shared static state and subinterpreters

FIXME: This section needs to be fleshed out.

Implications of the GIL

FIXME: This section needs to be fleshed out.

Memory allocation APIs

FIXME: This section needs to be fleshed out.

ABI Compatibility

The CPython C API does not guarantee ABI stability between minor releases (3.2, 3.3, 3.4, etc.). This means that, typically, if you build an extension module against one version of Python, it is only guaranteed to work with the same minor version of Python and not with any other minor versions.

Python 3.2 introduced the Limited API, with is a well-defined subset of Python’s C API. The symbols needed for the Limited API form the “Stable ABI” which is guaranteed to be compatible across all Python 3.x versions. Wheels containing extensions built against the stable ABI use the abi3 ABI tag, to reflect that they’re compatible with all Python 3.x versions.

CPython’s C API stability page provides detailed information about the API / ABI stability guarantees, how to use the Limited API and the exact contents of the “Limited API”.

Building binary extensions

FIXME: Cover the build-backends available for building extensions.

Building extensions for multiple platforms

If you plan to distribute your extension, you should providewheels for all the platforms you intend to support. These are usually built on continuous integration (CI) systems. There are tools to help you build highly redistributable binaries from CI; these includecibuildwheel and multibuild.

For most extensions, you will need to build wheels for all the platforms you intend to support. This means that the number of wheels you need to build is the product of:

count(Python minor versions) * count(OS) * count(architectures)

Using CPython’s Stable ABI can help significantly reduce the number of wheels you need to provide, since a single wheel on a platform can be used with all Python minor versions; eliminating one dimension of the matrix. It also removes the need to generate new wheels for each new minor version of Python.

Binary extensions for Windows

Before it is possible to build a binary extension, it is necessary to ensure that you have a suitable compiler available. On Windows, Visual C is used to build the official CPython interpreter, and should be used to build compatible binary extensions. To set up a build environment for binary extensions, installVisual Studio Community Edition- any recent version is fine.

One caveat: if you use Visual Studio 2019 or later, your extension will depend on an “extra” file, VCRUNTIME140_1.dll, in addition to theVCRUNTIME140.dll that all previous versions back to 2015 depend on. This will add an extra requirement to using your extension on versions of CPython that do not include this extra file. To avoid this, you can add the compile-time argument /d2FH4-. Recent versions of Python may include this file.

Building for Python prior to 3.5 is discouraged, because older versions of Visual Studio are no longer available from Microsoft. If you do need to build for older versions, you can set DISTUTILS_USE_SDK=1 and MSSdk=1 to force a the currently activated version of MSVC to be found, and you should exercise care when designing your extension not to malloc/free memory across different libraries, avoid relying on changed data structures, and so on. Tools for generating extension modules usually avoid these things for you.

Binary extensions for Linux

Linux binaries must use a sufficiently old glibc to be compatible with older distributions. The manylinux Docker images provide a build environment with a glibc old enough to support most current Linux distributions on common architectures.

Binary extensions for macOS

Binary compatibility on macOS is determined by the target minimum deployment system, e.g. 10.9, which is often specified with theMACOSX_DEPLOYMENT_TARGET environmental variable when building binaries on macOS. When building with setuptools / distutils, the deployment target is specified with the flag --plat-name, e.g. macosx-10.9-x86_64. For common deployment targets for macOS Python distributions, see the MacPython Spinning Wheels wiki.

Publishing binary extensions

Publishing binary extensions through PyPI uses the same upload mechanisms as publishing pure Python packages. You build a wheel file for your extension using the build-backend and upload it to PyPI usingtwine.

Avoid binary-only releases

It is strongly recommended that you publish your binary extensions as well as the source code that was used to build them. This allows users to build the extension from source if they need to. Notably, this is required for certain Linux distributions that build from source within their own build systems for the distro package repositories.

Weak linking

FIXME: This section needs to be fleshed out.

Additional resources

Cross-platform development and distribution of extension modules is a complex topic, so this guide focuses primarily on providing pointers to various tools that automate dealing with the underlying technical challenges. The additional resources in this section are instead intended for developers looking to understand more about the underlying binary interfaces that those systems rely on at runtime.

Cross-platform wheel generation with scikit-build

The scikit-build package helps abstract cross-platform build operations and provides additional capabilities when creating binary extension packages. Additional documentation is also available on the C runtime, compiler, and build system generator for Python binary extension modules.

Introduction to C/C++ extension modules

For a more in depth explanation of how extension modules are used by CPython on a Debian system, see the following articles:

Additional considerations for binary wheels

The pypackaging-native website has additional coverage of packaging Python packages with native code. It aims to provide an overview of the most important packaging issues for such projects, with in-depth explanations and references.

Examples of topics covered are non-Python compiled dependencies (“native dependencies”), the importance of the ABI (Application Binary Interface) of native code, dependency on SIMD code and cross compilation.