You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何构建含可导入子模块的Python C++扩展,使parent成为包?

解决方案

要让parent成为合法包并支持from parent.child import hello这类导入,需要解决两个核心问题:让Python将parent识别为包,以及让导入系统能找到parent.child子模块。以下是具体修改步骤:

1. 修改C++扩展代码(custom.cc)

关键修改点:

  • 给parent模块添加__path__属性,使其符合Python包的要求
  • 将child子模块注册到sys.modules字典,让导入系统能解析parent.child路径
  • 修正child模块的全名,使其与导入路径一致

修改后的完整代码:

#include <Python.h>
#include <string>

std::string hello() {
    return "Hi, World!";
}

static PyObject* hello_world(PyObject* self, PyObject* args) {
    return PyUnicode_FromString(hello().c_str());
}

static PyMethodDef ParentMethods[] = {
    {nullptr, nullptr, 0, nullptr}
};

static PyMethodDef ChildMethods[] = {
    {"hello", hello_world, METH_NOARGS, "Return a greeting message."},
    {nullptr, nullptr, 0, nullptr}
};

static PyModuleDef ChildModule = {
    PyModuleDef_HEAD_INIT,
    "parent.child",  // 修改为子模块的完整限定名称
    "A submodule of the parent module.",
    -1,
    ChildMethods,
    nullptr,
    nullptr,
    nullptr,
    nullptr
};

static PyModuleDef ParentModule = {
    PyModuleDef_HEAD_INIT,
    "parent",
    "A C++ extension module acting as a Python package.",
    -1,
    ParentMethods,
    nullptr,
    nullptr,
    nullptr,
    nullptr
};

PyMODINIT_FUNC PyInit_parent(void) {
    PyObject* parent_module = PyModule_Create(&ParentModule);
    if (!parent_module) {
        return nullptr;
    }

    // 1. 为parent模块添加__path__属性,使其成为合法包
    PyObject* path_list = PyList_New(0);
    if (!path_list) {
        Py_DECREF(parent_module);
        return nullptr;
    }
    if (PyModule_AddObject(parent_module, "__path__", path_list) != 0) {
        Py_DECREF(path_list);
        Py_DECREF(parent_module);
        return nullptr;
    }

    // 2. 创建child子模块
    PyObject* child_module = PyModule_Create(&ChildModule);
    if (!child_module) {
        Py_DECREF(parent_module);
        return nullptr;
    }

    // 3. 将child模块挂载到parent模块的属性中
    if (PyModule_AddObject(parent_module, "child", child_module) != 0) {
        Py_DECREF(child_module);
        Py_DECREF(parent_module);
        return nullptr;
    }

    // 4. 将child模块注册到sys.modules,让导入系统识别parent.child路径
    PyObject* sys_modules = PyImport_GetModuleDict();
    PyObject* child_full_name = PyUnicode_FromString("parent.child");
    if (!child_full_name) {
        Py_DECREF(parent_module);
        return nullptr;
    }
    PyDict_SetItem(sys_modules, child_full_name, child_module);
    Py_DECREF(child_full_name);

    return parent_module;
}

2. 调整setup.py(可选但推荐)

为了让setuptools正确识别parent作为包,可以添加packages声明:

from setuptools import Extension, setup

extension_mod = Extension('parent',
                          sources=['custom.cc'])

setup(name='parent',
      version='1.0',
      description='A C++ extension package with a submodule.',
      ext_modules=[extension_mod],
      packages=['parent'],
      package_dir={'parent': '.'}  # 指定包的根目录为当前目录
      )

3. 重新构建安装

执行以下命令重新编译安装扩展:

python setup.py build install

验证效果

现在可以正常执行两种导入方式:

# 方式1:通过父模块访问
import parent
parent.child.hello()  # 输出 'Hi, World!'

# 方式2:直接导入子模块方法
from parent.child import hello
hello()  # 输出 'Hi, World!'

原理说明

  • __path__属性:Python要求包必须包含__path__属性(一个列表),即使为空,只要存在就会被识别为包。
  • sys.modules注册:将parent.child模块注册到全局模块字典后,Python导入系统会直接从这里获取模块,而不会尝试从磁盘加载对应的.py或.so文件。
  • 子模块全名:将child模块的名称设置为parent.child,使其__name__属性与导入路径一致,避免导入时的名称冲突。

内容的提问来源于stack exchange,提问作者BlacKow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 14:37:12