4. 构建C和C++扩展

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

若要导入,共享库必须在 PYTHONPATH ,并且必须以模块名命名,并具有适当的扩展名。使用distutils时,将自动生成正确的文件名。

初始化函数具有签名:

PyObject *PyInit_modulename(void)

它返回完全初始化的模块或 PyModuleDef 实例。见 初始化C模块 有关详细信息。

对于只有ASCII名称的模块,必须为函数命名 PyInit_<modulename><modulename> 替换为模块的名称。使用时 多阶段初始化 ,允许使用非ASCII模块名。在这种情况下,初始化函数名是 PyInitU_<modulename><modulename> 使用python编码 微代码 用下划线替换的连字符编码。在 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. 用ditudil构建C和C++扩展

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

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 ,并生成名为 demobuild 目录。根据系统的不同,模块文件将结束在子目录中 build/lib.system 可能有一个名字 demo.sodemo.pyd .

setup.py ,所有执行都通过调用 setup 功能。这需要一个数量可变的关键字参数,上面的示例只使用一个子集。具体来说,该示例指定用于构建包的元信息,并指定包的内容。通常,包将包含其他模块,如python源模块、文档、子包等。请参阅中的distuils文档 分发python模块(旧版本) 要进一步了解distutils的特性,本节只解释构建扩展模块。

预先计算参数是常见的 setup() ,以便更好地构造驱动程序脚本。在上面的示例中, ext_modules 参数 setup() 是扩展模块的列表,每个扩展模块都是 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

这些行仅用于演示目的;distuils用户应该相信distuils获得了正确的调用权。

4.2. 分发扩展模块

成功构建扩展后,有三种方法可以使用它。

最终用户通常希望安装模块,他们通过运行以下命令来安装:

python setup.py install

模块维护人员应该生成源程序包;为此,他们运行:

python setup.py sdist

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

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

python setup.py bdist_rpm
python setup.py bdist_dumb