我有一个这样的 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
我需要改变什么才能使最后两个导入工作?
最佳答案
导入系统只允许将实际的包和模块作为点分模块名称的一部分直接导入,但是您的:
from .public_interface import *
hack 只是让
my_sub_module
my_module
的一个属性包,而不是用于导入系统的实际子模块。出于同样的原因,它会中断:from collections._sys import *
休息;是的,作为实现细节,
collections
包恰好导入sys
别名为 _sys
,但这实际上并没有使 _sys
collections
的子包,它只是 collections
上的众多属性之一包裹。从进口机械的角度来看,my_sub_module
不再是 my_module
的子模块比 _sys
属于 collections
;嵌套在 my_module
下的子目录中的事实无关紧要。也就是说,导入系统提供了一个钩子(Hook),允许您将其他任意目录视为包的一部分,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/XXXX
或 my_module/XXXX.py
,它应该寻找 my_module/public_interface/XXXX
或 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
是该规则的一个异常(exception);在 CPython 上,os
是一个具有属性 path
的普通模块(这恰好是模块 posixpath
或 ntpath
取决于平台),但您可以执行 import os.path
.这是因为 os
模块,在被导入时,显式地(并且巧妙地)填充 sys.modules
os.path
的缓存.这是不正常的,并且有性能成本; 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
这将允许人们使用
my_module.my_submodule
只做了import my_module
, 但这会强制任何 import
的 my_module
进口public_interface
和 my_sub_module
,即使 my_sub_module
中没有任何内容曾经使用过。 os.path
出于历史原因继续这样做(很久以前使用 os.path
API 和仅 import os
,并且很多代码依赖于这种不当行为,因为程序员很懒惰并且它有效),但是新代码不应该使用这个 hack .
关于python - 使另一个模块可以从我的 Python 模块中导入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57526479/