【问题标题】:Circular imports and class fields in python3python3中的循环导入和类字段
【发布时间】:2017-08-11 11:40:30
【问题描述】:

好的,我明白这个话题已经过时了,但我找不到我所问的特定问题的答案。

假设我们有一个非常简单的结构:两个文件,a.pyb.py,它们的内容是:

a.py

import b

class C:
    lal = 4

class A:
    kek = 12
    lol = b.B()

b.py

import a

class B:
    aa = a.C()

尝试运行python b.py,我们得到:

Traceback (most recent call last):
  File "b.py", line 1, in <module>
    import a
  File ".../a.py", line 1, in <module>
    import b
  File ".../b.py", line 3, in <module>
    class B:
  File ".../a.py", line 5, in A                                                                                  
    aa = a.C()
AttributeError: module 'a' has no attribute 'C'

但是如果我们将import bAFTER 移动到C 类,脚本将启动并且不会产生错误。

我在 SO 的任何答案中都没有发现任何提及这一点。这里的问题是:为什么会发生这种情况以及如何避免这种情况?

这对于 Django 框架来说是一个特别重要的问题。当我有很多模型时,我会尝试将它们拆分为多个文件。在那里进行循环导入非常容易。

【问题讨论】:

  • 我在这个领域没有受过多少教育,但我相信答案就在这里:docs.python.org/3/reference/import.html。我还建议使用“from module_x import class_y”而不是“import *”。避免这些问题有很大帮助。
  • 在加载程序执行模块代码之前,该模块将存在于 sys.modules 中。这是至关重要的,因为模块代码可能(直接或间接)导入自身;预先将其添加到 sys.modules 可以防止最坏情况下的无限递归和最佳情况下的多次加载。

标签: python django python-3.x python-import


【解决方案1】:

在 python 中,当你导入一个模块时,它首先导入在模块顶部定义的所有模块,如果一个模块不在 sys.modules 中,那么导入会在 sys.modules 中创建新的模块条目,然后执行模块中的代码。

因此,当您尝试在 a.py 中导入 b.py 模块时,如果模块未在 sys.modules 中列出,则它首先导入 b.py 中列出的所有模块是 a.py (import a)。并且模块 b.py 仍然没有完全执行,所以 b.py 模块没有添加到 sys.modules 中

之后,它尝试导入 a.py 并在 a.py 中尝试首先导入所有在 a.py 中导入的模块是 b.py

所以这是一个基本循环 a.py 尝试导入 b.py 和 b.py 尝试导入 a.py

对于这个问题,解决方案是在类中导入一个模块或类,而不是在模块的顶部

按照你的例子

a.py

class C:
    lal = 4

class A:
    import b
    kek = 12
    lol = b.B()

b.py

import a

class B:
    aa = a.C() 

a.py

import b
class C:
    lal = 4

class A:

    kek = 12
    lol = b.B()

b.py

class B:
    import a
    aa = a.C() 

详细information discussion

Python issue

【讨论】:

  • 正如我在回答中指出的那样,我知道这是一个解决方案。但我不明白为什么。而且这可能是一件坏事,因为所有导入都应该在文件的顶部。
  • 这不是真的。如果它是一个循环,那么就会有一个完全不同的错误。此外,您的修复(正如我所知道的那样有效)实际上并不能解决这个问题 - 它仍然是循环的。
  • @feakuru 循环导入错误不会给出任何其他错误,它会给出与您在问题中显示的相同的错误
【解决方案2】:

实际上@Kallz 已经提供了正确的答案,但是您有点固执:) 您正在创建以无限循环结束的循环引用。 Python 有一种机制来逐行避免这种循环。第一次打开 b.py - 它作为 '__main__' 加载到 sys.modules 中并开始加载。加载时会“导入a”。所以它寻找模块'a'。它找到它并将其放入 sys.modules 中。 sys.modules 现在有 '__main__' 和 'a'。它开始在遇到“import b”的地方加载模块“a”。所以它会找到“b.py”并将其放入 sys.modules 中。现在你有了 '__main__', 'a', 'b' 并且它开始在遇到 'import a' 的地方加载 'b' - 这次模块 'a' 已经在 sys.modules 中并且已经开始加载它了!所以 Python 知道发生了一些可疑的事情,你会得到一个 ImportError。

【讨论】:

  • 您可能会注意到,我没有收到ImportError。我收到了AttributeError。这表明python认为模块已经加载,只是它不包含C。这就是问题所在。
  • 在 p3.6 上,我在同一个问题集上遇到了 ImportError!
  • python 版本之间的加载似乎不同。你用的是哪一个?
  • 3.5.3.我不认为它应该不同
  • Python changlog,Python 3.6.0 beta2:bpo-21578:修复了使用无效关键字参数调用 ImportError 时的误导性错误消息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-13
  • 2012-03-16
  • 2022-10-05
  • 2019-05-13
  • 2014-11-29
  • 2017-01-19
  • 1970-01-01
相关资源
最近更新 更多