3. Building C and C++ Extensions

CPython的C扩展是共享库。在Linux上为.so文件,在Windows上为.pyd),导出初始化函数

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

初始化函数有声明:

PyObject* PyInit_modulename(void)

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

对于仅具有ASCII名称的模块,该函数必须命名为PyInit_<modulename>,其中<modulename>当使用Multi-phase initialization时,允许使用非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中的“一个库中的多个模块”

3.1. Building C and C++ Extensions with distutils

扩展模块可以使用distutils构建,它包含在Python中。因为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

将会编译build,并在demo.c目录下产生名为demo的扩展模块。依赖于构建系统,模块文件可能会在demo.so目录,名字可能是demo.pyd或者build/lib.system

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获得调用正确。

3.2. Distributing your extension modules

当成功构建一个扩展后,有三种方法来使用它。

最终用户典型地希望安装这个模块,通过执行命令

python setup.py install

模块维护者应该生成源包;这样做,他们跑

python setup.py sdist

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

如果已经成功构建了源代码分发,维护者还能创建二进制分发。依赖于不同的平台,执行下列命令中的某一个。

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