子模块的python导入路径如果放在名称空间包中

问题描述

我有一个用C编写的python模块,它有一个主模块和一个子模块(带点号的名称,不确定可以将其称为真正的子模块):

PyMODINIT_FUNC initsysipc(void) {
    PyObject *module = Py_InitModule3("sysipc",...);
    ...
    init_sysipc_light();
}

static PyTypeObject FooType = { ... };
PyMODINIT_FUNC init_sysipc_light(void) {
    PyObject *module = Py_InitModule3("sysipc.light",...);
    ...
    PyType_Ready(&FooType);
    PyModule_Addobject(module,"FooType",&FooType);
}

该模块编译为sysipc.so,当我将其放在当前目录中时,以下导入可以正常工作:

import sysipc
import sysipc.light
from sysipc.light import FooType

问题是我想将此模块放入命名空间包中,文件夹结构如下:

company/
company/__init__.py
company/dept/
company/dept/__init__.py
company/dept/sys/
company/dept/sys/__init__.py
company/dept/sys/sysipc.so

所有三个__init__.py仅包含标准的setuptool导入行:

__path__ = __import__('pkgutil').extend_path(__path__,__name__)

在当前目录中,无法进行以下导入:

from company.dept.sys import sysipc;
from company.dept.sys.sysipc.light import FooType;

在这种情况下,如何导入模块sysipc.light中定义的类型和方法

==================================

以实际错误更新:

我已经构建了sysipc.so,如果我在当前目录中以此模块运行python,则导入将按预期进行:

[root@08649fea17ef 2]# python2
Python 2.7.18 (default,Jul 20 2020,00:00:00)
[GCC 10.1.1 20200507 (Red Hat 10.1.1-1)] on linux2
Type "help","copyright","credits" or "license" for more information.
>>> import sysipc
>>> import sysipc.light
>>>

但是,如果我将其放入命名空间文件夹中,就像这样:

company/
company/__init__.py
company/dept
company/dept/__init__.py
company/dept/sys
company/dept/sys/sysipc.so
company/dept/sys/__init__.py

导入子模块将不起作用:

>>> from company.dept.sys import sysipc
>>> from company.dept.sys import sysipc.light
  File "<stdin>",line 1
    from company.dept.sys import sysipc.light
                                   ^
SyntaxError: invalid Syntax
>>> from company.dept.sys.sysipc import light
Traceback (most recent call last):
  File "<stdin>",line 1,in <module>
ImportError: cannot import name light
>>>

该模块是使用this simple code构建的,适用于python2。我也有same example for python3

解决方法

https://www.python.org/dev/peps/pep-0489/#multiple-modules-in-one-library引用:

要在一个共享库中支持多个Python模块,该库可以导出其他PyInit *符号与该库的文件名相对应的符号

请注意,该机制目前只能用于加载额外的模块,不能找到它们。 (这是加载程序机制的限制,此PEP不会尝试对其进行修改。)...

换句话说,您需要按如下所示重组项目,以使importlib能够在light包中找到子模块sysipc

company/__init__.py
company/dept/__init__.py
company/dept/sys/__init__.py
company/dept/sys/sysipc/__init__.py
company/dept/sys/sysipc/sysipc.so
company/dept/sys/sysipc/light.so -> sysipc.so  # hardlink

light.sosysipc.so之间的硬链接可以通过以下方式创建:

ln company/dept/sys/sysipc/sysipc.so company/dept/sys/sysipc/light.so

然后在company/dept/sys/sysipc/__init__.py中,使用以下命令从sysipc.so导入所有符号:

from .sysipc import *

此外,您需要将子模块C扩展初始化函数的名称从Python2的init_sysipc_light更改为init_light,对于Python3的名称从PyInit_sysipc_light更改为PyInit_light,因为importlib通过从动态模块中查找导出的PyInit_<module name>来加载模块,并且此处的模块名称仅为light,即,父包前缀不是(子)模块的一部分名称。

这是扩展代码(Python3)和一些测试功能:

#include <Python.h>

PyObject *sysipc_light_foo(PyObject *self,PyObject *args) {
  printf("[*] sysipc.light.foo\n");
  return PyLong_FromLong(0);
}

static PyMethodDef sysipc_light_methods[] = {
    {"foo",(PyCFunction)sysipc_light_foo,METH_VARARGS,"sysipc.light.foo function"},{NULL,NULL,NULL}
};

static struct PyModuleDef sysipc_light_module = {
    PyModuleDef_HEAD_INIT,"sysipc.light","sysipc child module",-1,sysipc_light_methods
};

PyMODINIT_FUNC PyInit_light(void)
{
    PyObject *module = NULL;

    module = PyModule_Create(&sysipc_light_module);

    return module;
}

PyObject *sysipc_bar(PyObject *self,PyObject *args) {
  printf("[*] sysipc.bar\n");
  return PyLong_FromLong(0);
}

static PyMethodDef sysipc_methods[] = {
    {"bar",(PyCFunction)sysipc_bar,"sysipc.bar function"},NULL}
};

static struct PyModuleDef sysipc_module = {
    PyModuleDef_HEAD_INIT,"sysipc","sysipc parent module",sysipc_methods
};

PyMODINIT_FUNC PyInit_sysipc(void)
{
    PyObject *module = NULL;

    module = PyModule_Create(&sysipc_module);

    PyInit_light();

    return module;
}

test.py:

#!/usr/bin/env python3

from company.dept.sys import sysipc
from company.dept.sys.sysipc import light

sysipc.bar() 
light.foo()

输出:

[*] sysipc.bar
[*] sysipc.light.foo
,

这里有两个问题:首先,Py_InitModule,朋友希望创建要导入的模块。提示是您传递的字符串不是模块的标准名称:Python使用它已经知道的名称来确定在sys.modules中放置新对象的位置。但是,您可以使用完全合格名称;其他__file__等魔术属性将具有正确的值。

第二个问题是,需要在包含模块上设置属性 light,才能进行from导入。

同时,没有理由拥有单独的初始化函数(解释器永远不会调用),并且将它们组合在一起避免了以后需要恢复指向模块的指针的情况:

static PyTypeObject FooType = { ... };
PyMODINIT_FUNC initsysipc(void) {
    PyObject *module = Py_InitModule3("sysipc",...);
    ...
    PyObject *const sub = Py_InitModule3("company.dept.sys.sysipc.light",...);
    ...
    PyType_Ready(&FooType);
    // PyModule_AddObject steals a reference:
    Py_INCREF(FooType);
    PyModule_AddObject(sub,"FooType",&FooType);
    Py_INCREF(sub);
    PyModule_AddObject(module,"light",sub);
}

也就是说,sysipc仍然不是一个合适的软件包:至少,它缺少__path__。如果那很重要,那么您可能更喜欢MEE's answer,它使用真实的(如果比较复杂的话)软件包体系结构。