使另一个模块可从我的 Python 模块导入
Making another module importable from my Python module
我的 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
下的子目录中的事实无关紧要。
也就是说,导入系统提供了一个挂钩,允许您将其他任意目录视为包的一部分,the __path__
attribute。默认情况下,__path__
只包含包本身的路径(因此 my_module
的 __path__
默认为 ['/absolute/path/to/my_module']
),但您可以根据需要以编程方式操作它;解析子模块时,它将仅搜索 __path__
的最终内容,就像导入顶级模块搜索 sys.path
一样。因此,要解决您的特殊情况(希望 public_interface
中的所有 packages/modules 都可以导入,而无需在导入行中指定 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
是此规则的例外;在 CPython 上,os
是一个带有属性 path
的普通模块(恰好是模块 posixpath
或 ntpath
,具体取决于平台),但您可以 import os.path
.这是有效的,因为 os
模块在被导入时显式地(和 hackily)填充了 os.path
的 sys.modules
缓存。这是不正常的,并且会产生性能成本; import os
必须 always import os.path
implicitly,即使从未使用过 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
,但这会 强制 import
of my_module
] 导入 public_interface
和 my_sub_module
,即使从未使用过 my_sub_module
中的任何内容。 os.path
由于历史原因继续这样做(很久以前只使用 import os
的 os.path
API,并且很多代码都依赖于这种不当行为,因为程序员很懒惰而且它有效),但是新代码不应该使用这个技巧。
我的 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
下的子目录中的事实无关紧要。
也就是说,导入系统提供了一个挂钩,允许您将其他任意目录视为包的一部分,the __path__
attribute。默认情况下,__path__
只包含包本身的路径(因此 my_module
的 __path__
默认为 ['/absolute/path/to/my_module']
),但您可以根据需要以编程方式操作它;解析子模块时,它将仅搜索 __path__
的最终内容,就像导入顶级模块搜索 sys.path
一样。因此,要解决您的特殊情况(希望 public_interface
中的所有 packages/modules 都可以导入,而无需在导入行中指定 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
是此规则的例外;在 CPython 上,os
是一个带有属性 path
的普通模块(恰好是模块 posixpath
或 ntpath
,具体取决于平台),但您可以 import os.path
.这是有效的,因为 os
模块在被导入时显式地(和 hackily)填充了 os.path
的 sys.modules
缓存。这是不正常的,并且会产生性能成本; import os
必须 always import os.path
implicitly,即使从未使用过 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
,但这会 强制 import
of my_module
] 导入 public_interface
和 my_sub_module
,即使从未使用过 my_sub_module
中的任何内容。 os.path
由于历史原因继续这样做(很久以前只使用 import os
的 os.path
API,并且很多代码都依赖于这种不当行为,因为程序员很懒惰而且它有效),但是新代码不应该使用这个技巧。