使用 Python 的稳定 ABI 识别包

Identifying Packages Using Python's Stable ABI

我有一个 Python 包,其中包含多个 C/C++ 扩展,构建为单个轮子。我试图了解如何确保轮子和它包含的共享库正确地宣传它们在特定的 API 版本中使用 stable ABI。我使用 setup.py 以这种方式 运行 构建包。

% python setup.py bdist_wheel --py-limited-api=cp34

认为 cp34 部分是我如何表明我正在使用稳定的 ABI,最多 Python 3.4 API.生成的轮子命名为 goober-1.2-cp34-abi3-linux_x86_64.whl。突出显示的部分显示 Python and ABI tags。没有 --py-limited-api,那部分是 cp38-cp38,匹配我的 Python 3.8。这是否足以宣传我的 wheel 应该适用于从 3.4 开始的所有 Python 3.x,而无需重新编译?我想我会指定 cp3 来指示所有 3.x 版本。

对于共享库,我以这种方式编译 C/C++ 源代码。

% gcc ... -DPy_LIMITED_API=0x03040000 ... blooper.c

在这种情况下,共享库被命名为blooper.cpython-38-x86_64-linux-gnu.so,没有任何迹象表明它支持稳定的ABI和 3.4 API。从 PEP 3149 我希望在名称中的某处看到它。否则,难道 Python 3.8 是唯一愿意导入此模块的版本吗?

谢谢。

这可能令人惊讶,但添加 --py-limited-api=cp34 只会更改轮子的名称,但不会更改其内容 - 即它将仍然是固定到 Python 版本的“普通”版本已建成。

第一步是创建一个 setup.py,它会生成一个使用稳定 API 并声明它的 C 扩展。据我所知 distutils 不支持稳定的 C-API,因此应该使用 setuptools

有一个最小的例子:

from setuptools import setup, Extension

my_extension = Extension(
            name='foo',
            sources = ["foo.c"],
            py_limited_api = True,
            define_macros=[('Py_LIMITED_API', '0x03040000')],
)

kwargs = {
      'name':'foo',
      'version':'0.1.0',
      'ext_modules':  [my_extension],
}

setup(**kwargs)

重要的细节是:

  • py_limited_api 应设置为 True,因此生成的扩展将具有正确的后缀(例如 abi3),一旦构建。
  • Py_LIMITED_API 宏应该设置为正确的值,否则将使用不稳定或错误的稳定 C-API。

由此产生的扩展后缀也可能令人惊讶。 CPython documentation 状态:

On some platforms, Python will look for and load shared library files named with the abi3 tag (e.g. mymodule.abi3.so). It does not check if such extensions conform to a Stable ABI. The user (or their packaging tools) need to ensure that, for example, extensions built with the 3.10+ Limited API are not installed for lower versions of Python.

“部分平台”是Linux和MacOS,可以看一下

from importlib.machinery import EXTENSION_SUFFIXES
print(EXTENSION_SUFFIXES)
# ['.cpython-38m-x86_64-linux-gnu.so', '.abi3.so', '.so'] on Linux
# ['.cp38-win_amd64.pyd', '.pyd']                         on Windows

这意味着在 Linux 上,结果将是 foo.abi3.so,而在 Windows 上只是 foo.pyx(参见例如 setuptools 中的 this code ).

现在,只是运行

python setup.py bdist_wheel

会构建一个扩展,它可以与任何 Python 版本 >= 3.4 一起使用,但是 pip 不会将它安装在 CPython-3.8 和 pymalloc 以外的任何地方在 Linux 上(因为轮子的名称是 foo-0.1.0-cp38-cp38m-linux_x86_64.whl)。这是文档中打包系统需要确保不会出现版本不匹配的部分。

要允许 pip 安装多个 python 版本,应使用 --py-limited-api-version:

创建 wheel
python setup.py bdist_wheel --py-limited-api=cp34

由于生成的名称foo-0.1.0-cp34-abi3-linux_x86_64.whlpip会知道,安装CPython>=3.4是安全的。


澄清一下:CPython 并不知道,后缀为 abi3.so(或 Windows 上的 .pyx)的 c 扩展名真的可以由解释器使用(它只是真诚地假设)- pip 确保安装了正确的版本。