Python 具有多个模块的扩展

Python extension with multiple modules

我正在为我编写的独立 C 库构建 Python 绑定。图书馆的文件布局如下:

<project root>
|
`- cpython
|  |
|  `- module1_mod.c
|  `- module2_mod.c
|  `- module3_mod.c
|
`- include
|  |
|  `- module1.h
|  `- module2.h
|  `- module3.h
|
`- src
|  |
|  `- module1.c
|  `- module2.c
|  `- module3.c
|
`- setup.py

我想获得一个 Python 包,这样我就可以在 my_package.module1my_package.module2 等名称空间中导入模块

到目前为止,这是我的setup.py

from os import path
from setuptools import Extension, setup


ROOT_DIR = path.dirname(path.realpath(__file__))
MOD_DIR = path.join(ROOT_DIR, 'cpython')
SRC_DIR = path.join(ROOT_DIR, 'src')
INCL_DIR = path.join(ROOT_DIR, 'include')
EXT_DIR = path.join(ROOT_DIR, 'ext')

ext_libs = [
    path.join(EXT_DIR, 'ext_lib1', 'lib.c'),
    # [...]
]

setup(
    name="my_package",
    version="1.0a1",
    ext_modules=[
        Extension(
            "my_package.module1",
            [
                path.join(SRC_DIR, 'module1.c',
                path.join(MOD_DIR, 'module1_mod.c',
            ] + ext_libs,
            include_dirs=[INCL_DIR],
            libraries=['uuid', 'pthread'],
        ),
    ],
)

导入 mypackage.module1 有效,但问题是 module2module3 也需要外部库(并非所有模块都需要),我假设如果我在其他模块中包含相同的外部库,我会变得很臃肿。

我查看了 Github 中的示例设置,但没有找到解决此问题的示例。

什么是组织构建的好方法?

编辑:这实际上是一个更严重的问题,因为我在 module1 中有 module2 中需要的符号,等等。 module2 中的对象需要 module1 中定义的对象类型。如果我创建单独的二进制文件而不包括每个依赖项的所有源,则这些符号在链接时将不可用,从而增加了跟踪哪个模块所需内容的冗余和复杂性。

经过几天深入研究 Python 错误报告和几乎没有记录的功能,我找到了解决这个问题的答案,它解决了多个外部依赖项和内部交叉链接。

解决方案是创建一个整体“模块”,其中定义了所有模块,然后在包初始化文件中用几行 Python 代码公开它们。

为此,我将模块源文件更改为头文件,将它们的大部分方法保持静态并且只公开 PyTypeObject 结构和我的对象类型结构,以便它们可以在其他模块中使用。

然后我将定义所有模块的 PyMODINIT_FUNC 函数移到“包”模块 (py_mypackage.c) 中,该模块还定义了一个空模块。 “包”模块定义为 _my_package.

最后,我在 __init__.py 脚本中添加了一些内部机制,该脚本从 .so 文件中提取模块符号并将它们作为包的模块公开。这记录在 Python docs :

import importlib.util
import sys

import _my_package


pkg_path = _my_package.__file__


def _load_module(mod_name, path):
    spec = importlib.util.spec_from_file_location(mod_name, path)
    module = importlib.util.module_from_spec(spec)
    sys.modules[mod_name] = module
    spec.loader.exec_module(module)

    return module


for mod_name in ('module1', 'module2', 'module3'):
    locals()[mod_name] = _load_module(mod_name, pkg_path)

新布局是这样的:

<project root>
|
`- cpython
|  |
|  `- my_package
|    |
|    `- __init__.py
|
|  `- py_module1.h
|  `- py_module2.h
|  `- py_module3.h
|  `- py_mypackage.c
|
`- include
|  |
|  `- module1.h
|  `- module2.h
|  `- module3.h
|
`- src
|  |
|  `- module1.c
|  `- module2.c
|  `- module3.c
|
`- setup.py

setup.py

setup(
    name="my_package",
    version="1.0a1",
    package_dir={'my_package': path.join(CPYTHON_DIR, 'my_package')},
    packages=['my_package'],
    ext_modules=[
        Extension(
            "_my_package",
            "<all .c files in cpython folder + ext library sources>",
            libraries=[...],
        ),
    ],
)

出于好奇,完整代码位于 https://notabug.org/scossu/lsup_rdf/src/e08da1a83647454e98fdb72f7174ee99f9b8297c/cpython(固定在当前提交)。