使另一个模块可以从我的 Python 模块中导入

Posted

技术标签:

【中文标题】使另一个模块可以从我的 Python 模块中导入【英文标题】:Making another module importable from my Python module 【发布时间】:2019-12-22 21:17:31 【问题描述】:

我有一个这样的 Python 模块目录结构:

my_module
|--__init__.py
|--public_interface
|  |--__init__.py
|  |--my_sub_module
|  |  |--__init__.py
|  |  |--code.py
|  |--some_more_code.py
|--other directories omitted

现在,public_interface 目录(以及其他几个目录)仅用于将代码组织成逻辑子单元,作为我和其他开发人员的指南。 my_module 的最终用户只能将其视为 my_module.my_sub_module,而中间没有 public_interface

我写了这些__init__.py 文件:

my_module.__init__.py:

from .public_interface import *

my_module.public_interface.__init__.py:

from . import my_sub_module
from .some_more_code import *

my_module.public_interface.my_sub_module.__init__.py:

from .code import *

只要用户只导入***模块,这就可以正常工作:

import my_module

my_module.my_sub_module.whatever  # Works as intended

但是,这不起作用:

from my_module import my_sub_module

也不:

import my_module.my_sub_module

我需要进行哪些更改才能使最后两个导入起作用?

【问题讨论】:

就是OOC,为什么是公用接口,而不是私有接口? 这更像是一个示例,以了解我想要实现的目标和原因。实际的目录名称不同,但对我和其他开发人员来说,公共接口和业务逻辑都有意义。为了实现我的目标,我只会在绝对必要的情况下更改它。 【参考方案1】:

导入系统只允许将实际的包和模块作为点分模块名称的一部分直接导入,但您的:

from .public_interface import *

hack 只是使 my_sub_module 成为 my_module 包的属性,而不是导入系统的实际子模块。出于同样的原因,它会中断:

from collections._sys import *

休息;是的,作为一个实现细节,collections 包碰巧导入了sys 别名为_sys,但这实际上并没有使_sys 成为collections 的子包,它只是@ 上的许多属性之一987654331@包。从导入机制的角度来看,my_sub_module 不再是my_module 的子模块,而不是_syscollections 的子模块;嵌套在my_module 下的子目录这一事实是无关紧要的。

也就是说,导入系统提供了一个挂钩,允许您将其他任意目录视为包的一部分,the __path__ attribute。默认情况下,__path__ 仅包含包本身的路径(因此my_module__path__ 默认为['/absolute/path/to/my_module']),但您可以根据需要以编程方式对其进行操作;解析子模块时,它只会搜索__path__ 的最终内容,就像导入***模块搜索sys.path 一样。因此,要解决您的特殊情况(希望public_interface 中的所有包/模块都可以导入,而无需在导入行中指定public_interface),只需将您的my_module/__init__.py 文件更改为具有以下内容:

import os.path
__path__.append(os.path.join(os.path.dirname(__file__), 'public_interface'))

所做的只是告诉导入系统,当import mymodule.XXXX 出现时(XXXX 是一个真实姓名的占位符),如果它找不到my_module/XXXXmy_module/XXXX.py,它应该寻找@ 987654352@ 或my_module/public_interface/XXXX.py。如果要先搜索public_interface,改成:

__path__.insert(0, os.path.join(os.path.dirname(__file__), 'public_interface'))

或者只有检查public_interface(所以my_module下的任何东西都不能导入),使用:

__path__[:] = [os.path.join(os.path.dirname(__file__), 'public_interface')]

完全替换__path__ 的内容。


旁注:您可能想知道为什么os.path 是该规则的一个例外;在 CPython 上,os 是一个具有属性 path 的普通模块(恰好是模块 posixpathntpath,具体取决于平台),但您可以使用 import os.path。这是因为os 模块在被导入时,会显式地(并且巧妙地)为os.path 填充sys.modules 缓存。这是不正常的,并且有性能成本; import os 必须始终 隐式导入 os.path,即使从未使用过来自 os.path 的任何内容。 __path__ 避免了这个问题;除非请求,否则不会导入任何内容。

可以通过让my_module/__init__.py包含:

import sys
from .public_interface import my_sub_module

sys.modules['my_module.my_sub_module'] = my_sub_module

这将允许人们在只使用import my_module 的情况下使用my_module.my_submodule,但这将强制任何importmy_module 导入public_interfacemy_sub_module,即使从未使用过来自my_sub_module 的任何内容。 os.path 出于历史原因继续这样做(很久以前只使用 import osos.path API,并且很多代码依赖于这种不当行为,因为程序员很懒惰并且它有效),但是新代码不应该使用这个技巧。

【讨论】:

以上是关于使另一个模块可以从我的 Python 模块中导入的主要内容,如果未能解决你的问题,请参考以下文章

从目录导入python中的用户定义模块

在 python 类中导入模块

在 Rails.application 初始化器中导入模块

在 Django 中导入 python 模块时出错

在 Python 中导入模块的问题

在完全不同的目录中导入 python 模块