相互进口;导入标准、"from" 和 "as" 语法之间的区别

Mutual imports; difference between import's standart, "from" and "as" syntax

给定这个简单的文件夹结构

/main.py
/project/a.py
/project/b.py

main.py 由 python 解释器执行并包含一行,import project.a.

ab是模块,需要相互导入。实现这一目标的一种方法是

import project.[a|b]

使用更深的嵌套文件夹结构时,您不希望每次使用模块时都写下整个路径,例如

import project.foo.bar
project.foo.bar.set_flag(project.foo.bar.SUPER)

from project import [a|b]import project.[a|b] as [a|b] 都会导致导入错误(当同时用于 ab 时)。

标准导入语法与 fromas 语法有何不同?为什么只有标准语法适用于相互导入?

更重要的是,是否有一种简单干净的方法来导入模块,允许相互导入并为它们分配更短的名称(理想情况下是模块基本名称,例如 barproject.foo.bar 的情况下) ?

当您执行 import project.afrom project import a 时,会发生以下情况:

  1. project.a 的模块对象被放入 sys.modules。这是一个将每个模块名称映射到其模块对象的字典,因此您将拥有 sys.modules = {..., 'p.a': <module 'p.a' from '.../project/a.py'>, ...}.
  2. 模块代码已执行。
  3. a 属性添加到 project

现在,这是 import project.afrom project import a 之间的区别:

  • import project.a 只是寻找 sys.modules['project.a']。如果存在,它将使用 sys.modules['project'].
  • 绑定名称 project
  • from project import a 查找 sys.modules['project'],然后检查 project 模块是否具有 a 属性。

    您可以将 from project import a 视为等同于以下两行:

    import project.a  # not problematic
    a = project.a     # causes an error
    

这就是为什么您仅在执行 from project import a 时才会看到异常:sys.modules['project.a'] 存在,但 project 还没有 a 属性。


最快的解决方案就是简单地避免循环导入。但如果你不能,那么通常的策略是:

  • 尽可能晚导入。假设您的 a.py 看起来像这样:

    from project import b
    
    def something():
        return b.something_else()
    

    改写如下:

    def something():
        from project import b
        return b.something_else()
    

    当然,您必须在所有函数中重复 imports。

  • 使用延迟导入。惰性导入不是 Python 的标准功能,但您可以找到许多实现。它们使用 "import as late as possible" 原理工作,但它们添加了一些语法糖让您编写更少的代码。

  • 作弊,使用sys.modules,像这样:

    import sys
    import project.a
    a = sys.modules['project.a']
    

    非常un-pythonic,但有效。

显然,无论您选择什么解决方案,在模块完全加载之前,您都无法从 ab 访问属性。