使另一个模块可从我的 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 包恰好导入了别名 _syssys,但这实际上并没有使 _sys 成为 collections 的子包,它只是 collections 包的众多属性之一。从导入机器的角度来看,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 中的所有 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/XXXXmy_module/XXXX.py,它应该查找 my_module/public_interface/XXXXmy_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 模块在被导入时显式地(和 hackily)填充了 os.pathsys.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_interfacemy_sub_module,即使从未使用过 my_sub_module 中的任何内容。 os.path 由于历史原因继续这样做(很久以前只使用 import osos.path API,并且很多代码都依赖于这种不当行为,因为程序员很懒惰而且它有效),但是新代码不应该使用这个技巧。