【问题标题】:In a Python package's __init__.py file, is there a way of detecting if a package was executed directly?在 Python 包的 __init__.py 文件中,有没有办法检测包是否被直接执行?
【发布时间】:2021-03-06 19:32:44
【问题描述】:

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

这是一个用例:

在 Python 中,一个常见的习惯用法是在模块的 __init__.py 文件中添加 import 语句,例如“扁平化”模块的命名空间并使其子模块可以直接访问。不幸的是,这样做会使加载特定子模块变得非常缓慢,因为在 __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!如果兄弟姐妹很大,这可能会无缘无故减慢速度。

我想写 module/__init__.py 类似的东西:

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

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

【问题讨论】:

  • 参考What does if __name__ == "__main__": do? - 你可能正在寻找if __name__ != '__main__':,如果直接执行,则运行相反的效果,这可能是你正在寻找的。​​span>
  • 是的,这与我需要的非常相似,但它不太有效(据我了解)。使用import module 导入模块时,__name__ 设置为module,这与使用import module.submodule 时无法区分,后者也将其设置为module
  • 这仅仅是因为执行import module.submodule 隐含地首先执行import module - 这就是为什么在执行import module.submodule 之后,module 被分配了对module.submodule 父模块的适当引用(即module)。
  • 这就是我的想法,但我不知道从这里去哪里。有什么方法可以在某处访问整个 module.submodule 字符串吗?我查看了所有“dunder”变量,但似乎没有任何帮助。
  • 基本上没有办法停止执行module/__init__,py 中的任何内容,也不会在从任何地方执行import module.submodule 时向其显示任何特殊指示。

标签: python python-3.x module init


【解决方案1】:

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

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

为了更详细地说明这个示例,假设可以通过简单地执行from module import SiblingClassmodule.sibling.SiblingClass 导入SiblingClass,因为module/__init__.py 文件执行from .sibling import * 语句来创建该绑定。但是,如果执行 import module.submodule 导致避免了扁平化导入,我们会得到以下情况:

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

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

您可以通过将print 语句放入文件中来验证这一事实,导入相应的模块以查看产生的输出,并看到在随后导入该模块时不会产生进一步的输出(相关:@987654321 @)。

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

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

【讨论】:

  • 这很有意义,感谢您的详细回答。对于这个特定的问题,我决定创建一个名为 module.all 的额外文件,并在需要此类功能时执行 from module.all import *
【解决方案2】:

这不是一个完整的解决方案,但standalone py.test (ignore __init__.py files) 建议设置一个全局标志来检测何时处于测试状态。这至少可以纠正测试的问题,前提是相关模块不相互调用。

【讨论】:

    猜你喜欢
    • 2017-02-25
    • 1970-01-01
    • 2022-10-07
    • 1970-01-01
    • 2015-11-23
    • 2021-12-28
    • 2018-06-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多