在 Python 包的 __init__.py 文件中,有没有办法检测包是否被直接执行?

In a Python package's __init__.py file, is there a way of detecting if a package was executed directly?

我想要一种方法来检测我的模块是否被直接执行,如 import modulefrom module import * 而不是 import module.submodule(它也执行 module) ,并在 module__init__.py.

中访问此信息

这是一个用例:

在Python中,一个常见的习惯用法是在模块的__init__.py文件中添加导入语句,例如“扁平化”模块的命名空间并使其子模块可直接访问。不幸的是,这样做会使加载特定子模块非常缓慢,因为在 __init__.py 中导入的所有其他兄弟姐妹也会执行。

例如:

module/
   __init__.py
   submodule/
      __init__.py
      ...
   sibling/
      __init__.py
      ...

通过添加到 module/__init__.py:

from .submodule import *
from .sibling import *

模块的用户现在可以在不知道包结构细节的情况下访问子模块中的定义(即 from module import SomeClass,其中 SomeClass 在 submodule 的某处定义并暴露在其拥有 __init__.py 个文件)。

但是,如果我现在 运行 submodule 直接(如 import module.submodule,通过调用 python3 -m module.submodule,或者甚至通过 pytest 间接地),我也会不可避免地,执行sibling!如果 sibling 很大,这可能会无缘无故地减慢速度。

我想写 module/__init__.py 像这样的东西:

if __???__ == 'module':
   from .submodule import *
   from .sibling import *

其中 __???__ 给出了导入的完全限定名称。任何类似的机制也可以工作,虽然我最感兴趣的是一般情况(检测直接执行)而不是这个具体的例子。

当我们考虑导入系统的实际工作方式时,如果确实可能的话,我们希望的是会导致未定义的行为(从某种意义上说扁平名称是否可以从 module 导入)。

假设,如果您想要实现的目标是可能的,其中一些 __dunder__ 将消除用于导入 module/__init__.py 的导入语句的歧义(例如 import modulefrom module import *, vs import module.submodule。对于第一种情况,module 可能会触发后续(缓慢的)导入以生成所需导入的“扁平化”版本,而后一种情况 (import module.submodule)将避免这种情况,因此 module 将不包含任何“扁平化”导入的分配。

为了进一步说明这个例子,假设可以通过在 module/__init__.py 文件执行 from .sibling import * 时简单地执行 from module import SiblingClassmodule.sibling.SiblingClass 导入 SiblingClass创建该绑定的语句。但是,如果执行 import module.submodule 导致避免了扁平化导入,我们会得到以下场景:

import module.submodule
# module.submodule gets imported
from module import SiblingClass
# ImportError will occur

这是为什么?这仅仅是由于 Python 导入文件的方式 - 源文件被完整执行一次以将导入、函数和 class 声明分配给指定的名称,并注册到 sys.modules以其进口名称。再次导入模块将 不会 再次执行该文件,因此如果 from .sibling import * 语句在其初始导入期间未执行(即 import module.submodule),它将永远不会被执行在同一模块的后续导入期间再次执行,因为分配给其在 sys.module 中的模块条目的初始导入生成的副本被返回(除非模块被手动重新加载,模块的代码将再次执行)。

您可以通过将 print 语句放入文件中来验证这一事实,导入相应的模块以查看产生的输出,并看到在随后导入该模块时不会产生进一步的输出(相关:What happens when a module is imported twice?).

实际上,问题中描述的所需功能无法在 Python 中实现。

关于此主题的相关主题:How to only import sub module without exec __init__.py in the package

这不是一个完整的解决方案,但 建议设置一个全局标志以在测试时进行检测。如果相关模块不相互调用,这至少可以解决测试问题。