【问题标题】:Circular dependency - when does it terminate?循环依赖——它什么时候终止?
【发布时间】:2021-03-23 07:49:32
【问题描述】:

我无法理解 python 如何管理imports。

假设我有以下应用程序结构:

application/
- application.py
- model/
-- __init__.py
-- user.py

假设application.py 文件在创建数据库后导入模型模块,如下所示:

db = SQLAlchemy(application) 
import model

假设model 模块像这样导入user.py 文件:

import user

最后,假设user.py 文件从application.py 文件中导入db 实例,如下所示:

from application import db

这对我来说似乎是一种循环依赖,因为application.py 文件间接需要user.py 文件,但user.py 文件需要application.py 文件中的db 实例。

我知道这段代码在我测试时确实有效,但有人可以准确解释 Python 是如何处理这个问题的,以及它何时终止循环周期。

总结这个问题,当user.py 文件从application.py 文件导入db 时,在我看来它也会调用导入model 模块,这会创建一个无限循环。

【问题讨论】:

    标签: python python-3.x import


    【解决方案1】:

    循环导入本身不一定是问题。只有循环依赖。您可以通过一些简单的实验来了解循环导入是如何解决的:

    mymod1.py

    import sys
    print("1. from mymod1:", [x for x in sys.modules if x.startswith("mymod")])
    import mymod2
    print("2. from mymod1:", [x for x in sys.modules if x.startswith("mymod")])
    

    mymod2.py

    import sys
    print("1. from mymod2:", [x for x in sys.modules if x.startswith("mymod")])
    import mymod3
    print("2. from mymod2:", [x for x in sys.modules if x.startswith("mymod")])
    

    mymod3.py

    import sys
    print("1. from mymod3:", [x for x in sys.modules if x.startswith("mymod")])
    import mymod1
    print("2. from mymod3:", [x for x in sys.modules if x.startswith("mymod")])
    

    现在我们有一个循环mymod1 -> mymod2 -> mymod3 -> mymod1。输入 REPL 并观察会发生什么:

    >>> import mymod1
    1. from mymod1: ['mymod1']  # before mymod1 imported mymod2. note mymod1 is already there!
    1. from mymod2: ['mymod1', 'mymod2']  # in mymod2 now, before importing mymod3
    1. from mymod3: ['mymod1', 'mymod2', 'mymod3']  # before mymod3 imports mymod1
    2. from mymod3: ['mymod1', 'mymod2', 'mymod3']  # mymod3 exit
    2. from mymod2: ['mymod1', 'mymod2', 'mymod3']  # mymod2 exit
    2. from mymod1: ['mymod1', 'mymod3', 'mymod2']  # mymod1 exit
    

    这里的关键见解是模块实例本身在它完成执行之前已经存在于sys.modules 中。这意味着它可以再次导入并返回现有对象,而无需再次执行所有模块级代码。

    包的子模块中的组件具有相互依赖关系是很自然的。当模块范围的代码开始实际诸如尝试连接到数据库之类的事情时,问题大多会出现,因此请尽量避免直接在模块级别编写任何脚本。

    循环导入错误的原因是在模块完成初始化之前需要模块命名空间中的某些内容。在这种情况下,模块本身存在,但您尝试访问的名称可能还不存在。

    # mymod.py
    import sys
    print("var" in vars(sys.modules["mymod"]))
    var = "I'm a name in mymod namespace"
    print("var" in vars(sys.modules["mymod"]))
    

    导入mymod 会打印False,然后是True,模块的命名空间本身仍在变化,因为导入正在执行过程中。

    精明的读者可能已经注意到 mymod2mymod3 在输出中交换了位置:

    2. from mymod2: ['mymod1', 'mymod2', 'mymod3']
    2. from mymod1: ['mymod1', 'mymod3', 'mymod2']
                               |_____ wtf? _____|
    

    这其实绝非偶然!作为加载模块的最后一步,导入机制将实际模块从sys.modules 中拉出。如果您在 REPL 中再次检查,mymod1 现在将是最后一个。

    >>> import mymod1
    ...
    >>> [x for x in sys.modules if x.startswith("mymod")]
    ['mymod3', 'mymod2', 'mymod1']
    

    我不会描述导入系统为什么会这样做,因为它与问题并不真正相关,但有兴趣了解原因的用户应该查看this mailing list post from Guido

    【讨论】:

      猜你喜欢
      • 2012-06-25
      • 2013-09-22
      • 2012-05-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-19
      • 2020-10-30
      • 2018-12-10
      相关资源
      最近更新 更多