简体   繁体   English

Python 扩展多模块

[英]Python extension with multiple modules

I am building Python bindings for a standalone C library that I wrote.我正在为我编写的独立 C 库构建 Python 绑定。 The file layout of the library is as following:该库的文件布局如下:

<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

I want to obtain a Python package so I can import modules in a namespace such as my_package.module1 , my_package.module2 , etc.我想获得一个 Python package 以便我可以在my_package.module1my_package.module2等命名空间中导入模块。

This is my setup.py so far:到目前为止,这是我的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'],
        ),
    ],
)

Importing mypackage.module1 works but the problem is that the external libraries are also needed by module2 and module3 (not all of them for all the modules), and I assume that if I include the same external libs in the other modules, I would get a lot of bloat.导入mypackage.module1有效,但问题是module2module3也需要外部库(并非所有模块都需要外部库),我假设如果我在其他模块中包含相同的外部库,我会得到很多膨胀。

I looked around sample setups in Github but haven't found an example resolving this problem.我查看了 Github 中的示例设置,但没有找到解决此问题的示例。

What is a good way to organize my builds?什么是组织我的构建的好方法?

EDIT: This is actually a more severe problem in that I have symbols in module1 that are needed in module2 , etc. Eg an object in module2 requires an object type defined in module1 .编辑:这实际上是一个更严重的问题,因为我在module1中有module2 module2的 object 需要在 module1 中定义的module1类型。 If I create separate binaries without including all sources for each dependency, the symbols won't be available at linking time, thus increasing redundancy and complexity of keeping track of what is needed for which module.如果我创建单独的二进制文件而不包括每个依赖项的所有源,则符号在链接时将不可用,从而增加了跟踪哪个模块所需内容的冗余和复杂性。

After a couple of days of digging into Python bug reports and scarcely documented features, I found an answer to this, which resolved both the multiple external dependencies and the internal cross-linking.在深入研究 Python 错误报告和几乎没有文档记录的功能后,我找到了一个答案,它解决了多个外部依赖项和内部交叉链接。

The solution was to create a monolithic "module" with all the modules defined inside it, then exposing them with a few lines of Python code in a package initialization file.解决方案是创建一个整体“模块”,其中定义了所有模块,然后在 package 初始化文件中使用几行 Python 代码公开它们。

To do this I changed the module source files to header files, maintaining most of their methods static and only exposing the PyTypeObject structs and my object type structs so they can be used in other modules.为此,我将模块源文件更改为 header 文件,维护他们的大部分方法 static 并且只公开PyTypeObject结构和我的 object 类型的结构,因此它们可以用于其他模块。

Then I moved the PyMODINIT_FUNC functions defining all the modules in a "package" module ( py_mypackage.c ), which also defines an empty module.然后我移动了PyMODINIT_FUNC函数,它定义了“包”模块( py_mypackage.c )中的所有模块,该模块还定义了一个空模块。 the "package" module is defined as _my_package . “包”模块定义为_my_package

Finally I added some internal machinery to an __init__.py script that extracts the module symbols from the.so file and exposes them as modules of the package.最后,我在__init__.py脚本中添加了一些内部机制,该脚本从.so 文件中提取模块符号并将它们公开为 package 的模块。 This is documented in the Python docs :这记录在Python 文档中:

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)

The new layout is thus:因此,新的布局是:

<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

And 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=[...],
        ),
    ],
)

For the curious, the complete code is at https://notabug.org/scossu/lsup_rdf/src/e08da1a83647454e98fdb72f7174ee99f9b8297c/cpython (pinned at the current commit).出于好奇,完整的代码位于https://notabug.org/scossu/lsup_rdf/src/e08da1a83647454e98fdb72f7174ee99f9b8297c/cpython (固定在当前提交处)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM