如何构建含可导入子模块的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
相关产品推荐
相关产品推荐

