如果放入命名空间包中,则子模块的python导入路径

Posted

技术标签:

【中文标题】如果放入命名空间包中,则子模块的python导入路径【英文标题】:python import path for sub modules if put in namespace package 【发布时间】:2021-01-06 18:13:33 【问题描述】:

我有一个用 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。

【问题讨论】:

首先感谢您提出的好问题。看起来对我和我的工作很有见地。在这里,我的问题是你介意告诉你如何说“在导入之后没有问题”,比如import sysipc,因为据我所知,我们将构建 C 文件并将其安装到我们想要的 python 模块中(而不是转换为.so) 并且可以在任何文件中调用。你能单独解释那部分吗?需要一些关于如何调整的信息? 您能否在尝试导入模块时提供错误消息?我试图复制这个问题,但对我来说,导入工作正常(除了自动完成,因为我没有包含存根) 我已经用错误信息和示例代码更新了问题。 【参考方案1】:

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

为了在一个共享库中支持多个 Python 模块,库可以导出其他 PyInit* 符号除了对应于库文件名的符号

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

也就是说,你需要对项目进行如下重构,让importlib能够在sysipc包中找到子模块light

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 *

此外,对于 Python2,您需要将子模块 C 扩展 init 函数的名称从 init_sysipc_light 更改为 init_light,或者对于 Python3,从 PyInit_sysipc_light 更改为 PyInit_light,因为 importlib 通过查找加载模块对于从动态模块导出的PyInit_&lt;module name&gt;,这里的模块名称只有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, 0, 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, METH_VARARGS, "sysipc.bar function",
    NULL, NULL, 0, NULL
;

static struct PyModuleDef sysipc_module = 
    PyModuleDef_HEAD_INIT,
    "sysipc",
    "sysipc parent module",
    -1,
    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

【讨论】:

相关:***.com/a/52729181/5769463 __init__.py 可用于插入一个加载器,该加载器将加载共享对象并导入正确的初始化函数,而不是创建链接【参考方案2】:

这里有两个问题:第一,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。

【讨论】:

我对这种方法的唯一不满是它迫使扩展知道并硬编码模块在包层次结构中的预期位置。 @MEE:如果您想以这种方式移植,您可以随时检查***模块的__name__ 我可以只使用PyObject *const sub = Py_InitModule3("sysipc.light", ...); 而不是完整的模块名称吗? @fluter:这基本上就是 MEE 的观点——不幸的是,你必须完全限定它。我的意思是,如果需要,您可以计算它。 我的意思是如果我只输入“sysipc.light”会怎样?

以上是关于如果放入命名空间包中,则子模块的python导入路径的主要内容,如果未能解决你的问题,请参考以下文章

如何导入 Python 命名空间包的所有子模块?

在 Python 中,如何从导入的模块访问主模块的命名空间?

Python Day27

如何从另一个包中只导入一个函数,而不加载整个命名空间

Python中的模块

如何导入安装在站点包中的模块