4. 构建 C 和 C++ 扩展 — Python 文档
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.so
或 demo.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