4. 构建 C 和 C++ 扩展 — Python 文档

来自菜鸟教程
Python/docs/3.9/extending/building
跳转至:导航、​搜索

4. 构建 C 和 C++ 扩展

CPython 的 AC 扩展是一个共享库(例如 Linux 上的 .so 文件,Windows 上的 .pyd),它导出 初始化函数

为了可导入,共享库必须在 PYTHONPATH 上可用,并且必须以模块名称命名,并带有适当的扩展名。 使用 distutils 时,会自动生成正确的文件名。

初始化函数具有签名:

PyObject *PyInit_modulename(void)

它返回一个完全初始化的模块,或者一个 PyModuleDef 实例。 有关详细信息,请参阅 初始化 C 模块

对于只有 ASCII 名称的模块,函数必须命名为 PyInit_<modulename>,用模块名称替换 <modulename>。 使用 多阶段初始化 时,允许使用非 ASCII 模块名称。 在这种情况下,初始化函数名称是 PyInitU_<modulename>,其中 <modulename> 使用 Python 的 punycode 编码,连字符替换为下划线。 在 Python 中:

def initfunc_name(name):
    try:
        suffix = b'_' + name.encode('ascii')
    except UnicodeEncodeError:
        suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
    return b'PyInit' + suffix

通过定义多个初始化函数,可以从单个共享库中导出多个模块。 但是,导入它们需要使用符号链接或自定义导入器,因为默认情况下只能找到与文件名对应的函数。 有关详细信息,请参阅 PEP 489 中的 “一个库中的多个模块” 部分。

4.1. 使用 distutils 构建 C 和 C++ 扩展

可以使用 Python 中包含的 distutils 构建扩展模块。 由于 distutils 也支持创建二进制包,用户不一定需要编译器和 distutils 来安装扩展。

distutils 包包含一个驱动程序脚本,setup.py。 这是一个普通的 Python 文件,在最简单的情况下,它可能如下所示:

from distutils.core import setup, Extension

module1 = Extension('demo',
                    sources = ['demo.c'])

setup (name = 'PackageName',
       version = '1.0',
       description = 'This is a demo package',
       ext_modules = [module1])

有了这个 setup.py 和一个文件 demo.c,运行

python setup.py build

将编译 demo.c,并在 build 目录中生成名为 demo 的扩展模块。 根据系统的不同,模块文件将在子目录 build/lib.system 中结束,并且可能具有类似 demo.sodemo.pyd 的名称。

setup.py中,所有的执行都是通过调用setup函数来完成的。 这需要可变数量的关键字参数,上面的示例仅使用其中的一个子集。 具体来说,该示例指定了构建包的元信息,并指定了包的内容。 通常,一个包会包含额外的模块,比如 Python 源模块、文档、子包等。 请参考 Distributing Python Modules (Legacy version) 中的 distutils 文档,了解更多关于 distutils 的特性; 本节仅解释构建扩展模块。

通常预先计算 setup() 的参数,以更好地构建驱动程序脚本。 在上面的例子中,setup()ext_modules 参数是一个扩展模块列表,每个模块都是 Extension 的一个实例。 在示例中,该实例定义了一个名为 demo 的扩展名,它是通过编译单个源文件 demo.c 来构建的。

在许多情况下,构建扩展更为复杂,因为可能需要额外的预处理器定义和库。 这在下面的示例中进行了演示。

from distutils.core import setup, Extension

module1 = Extension('demo',
                    define_macros = [('MAJOR_VERSION', '1'),
                                     ('MINOR_VERSION', '0')],
                    include_dirs = ['/usr/local/include'],
                    libraries = ['tcl83'],
                    library_dirs = ['/usr/local/lib'],
                    sources = ['demo.c'])

setup (name = 'PackageName',
       version = '1.0',
       description = 'This is a demo package',
       author = 'Martin v. Loewis',
       author_email = 'martin@v.loewis.de',
       url = 'https://docs.python.org/extending/building',
       long_description = '''
This is really just a demo package.
''',
       ext_modules = [module1])

在这个例子中,setup() 被调用时带有额外的元信息,推荐在必须构建分发包时使用。 对于扩展本身,它指定了预处理器定义、包含目录、库目录和库。 根据编译器的不同,distutils 以不同的方式将此信息传递给编译器。 例如,在 Unix 上,这可能会导致编译命令

gcc -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -fPIC -DMAJOR_VERSION=1 -DMINOR_VERSION=0 -I/usr/local/include -I/usr/local/include/python2.2 -c demo.c -o build/temp.linux-i686-2.2/demo.o

gcc -shared build/temp.linux-i686-2.2/demo.o -L/usr/local/lib -ltcl83 -o build/lib.linux-i686-2.2/demo.so

这些线路仅用于演示目的; distutils 用户应该相信 distutils 会正确调用。


4.2. 分发扩展模块

成功构建扩展后,可以通过三种方式使用它。

最终用户通常希望安装该模块,他们通过运行

python setup.py install

模块维护者应该制作源码包; 为此,他们运行

python setup.py sdist

在某些情况下,源代码分发中需要包含其他文件; 这是通过 MANIFEST.in 文件完成的; 有关详细信息,请参阅 指定要分发的文件

如果源分发已成功构建,维护人员还可以创建二进制分发。 根据平台,可以使用以下命令之一来执行此操作。

python setup.py bdist_wininst
python setup.py bdist_rpm
python setup.py bdist_dumb