从 __init__.py 中访问导入语句(或导入子模块时如何不 运行 __init__.py)

access to import statement from within __init__.py (or how to not run __init__.py when importing submodule)

我的包裹看起来像这样:

/mypackage
|-- __init__.py
|-- moduleA.py
|-- moduleB.py
|-- oldModule.py

__init__.py 看起来像这样:

from mypackage.moduleA import abc
from mypackage.moduleB import jkl

这允许用户简单地编码如下:

import mypackage as mpkg

mpkg.abc()
mpkg.jkl()

默认情况下(使用上述 import)用户无法访问 oldModule.py 中的功能。
这是因为 95% 的用户不需要那个东西(它很旧)。


需要oldModule功能的那5%的用户,可以这样写:

from mypackage.oldModule import xyz
xyz()

许多 "oldModule" 用户不需要访问 __init__.py

导入的功能

问题是,当用户只执行 from mypackage.oldModule import xyz
时,很明显整个 __init__.py 也在 运行。

有什么办法可以吗

谢谢。

无法完全按照您的要求进行操作。当您导入 some_package.some_module 时,包总是在子模块之前完全加载。

但可能有一些方法可以缓解这在您的案例中引起的问题。如果你想避免在用户寻找 oldModule 时导入 moduleAmoduleB 子模块,你或许可以让他们的加载延迟,而不是急切。

从 Python 3.7 开始,PEP 562 使模块具有名为 __getattr__ 的函数,该函数将像 class 中定义的同名方法一样工作.当查找模块的属性但未找到时,将使用名称调用 __getattr__ 函数。

所以在您的 __init__.py 文件中,您可以:

# from mypackage.moduleA import xyz   don't do these imports unconditionally any more
# from mypackage.moduleB import abc

def __getattr__(name):
    global xyz, abc
    if name == 'xyz':
        from .moduleA import xyz
        return xyz
    elif name == 'abc':
        from .moduleB import abc
        return abc
    raise AttributeError(f"module {__name__} has no attribute {name}")

如果您有更多名称需要以这种方式保护(不仅仅是两个),您可能需要编写更通用的代码来处理名称列表(可能每个子模块一个)并导入它们正确使用 importlib 并直接写入包的 __dict__。这比尝试使用 global 语句并为每个名称编写单独的 if 分支更方便。