我有这样的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
要使这最后两个导入有效,我需要更改什么?
答案 0 :(得分:1)
导入系统仅允许将实际的软件包和模块作为点分模块名称的一部分直接导入,但是您必须:
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
下的子目录中的事实无关紧要。
也就是说,导入系统提供了一个挂钩,使您可以将其他任意目录视为软件包the __path__
attribute的一部分。默认情况下,__path__
仅包含包本身的路径(因此my_module
的{{1}}默认为__path__
),但是您可以根据需要以编程方式对其进行操作;解析子模块时,它将仅搜索['/absolute/path/to/my_module']
的最终内容,就像导入顶级模块搜索__path__
一样。因此,要解决您的特殊情况(要使sys.path
中的所有程序包/模块都可导入而无需在导入行中指定public_interface
),只需更改您的public_interface
文件,使其具有以下内容: / p>
my_module/__init__.py
所有操作都告诉导入系统,当import os.path
__path__.append(os.path.join(os.path.dirname(__file__), 'public_interface'))
出现时(import mymodule.XXXX
是实名的占位符),如果找不到XXXX
或{{1 }},它应该寻找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')]
是该规则的例外;在CPython上,__path__
是具有属性os.path
的普通模块(根据平台的不同,它恰好是模块os
或path
),但是您可以执行{{1 }}。之所以可行,是因为posixpath
模块在被导入时显式地(并且很隐秘地)填充了ntpath
的{{1}}缓存。这是不正常的,而且会降低性能。 import os.path
必须始终隐式导入os
,即使从未使用过sys.modules
中的任何内容。 os.path
避免了这个问题;除非要求,否则不会导入任何内容。
通过使import os
包含以下内容,您可以达到相同的结果:
os.path
这将允许人们使用仅完成os.path
的{{1}},但是会{em>强制 __path__
中的任何my_module/__init__.py
来导入{{ 1}}和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
的{{1}} API,并且很多代码都依赖于这种不当行为,因为程序员是懒惰的并且可以工作),但是新代码不应使用此hack。