循环导入混乱

时间:2014-07-31 16:25:24

标签: python python-2.7

我有三个文件:

1)one.py

from two import test

print(test)

2)two.py

test = [1, 2]

import three

print(test)

3)three.py

from two import test

test.append(3)

当我运行命令python two.py时,我得到:

[1, 2]
[1, 2]

当我运行命令python one.py时,我得到:

[1, 2, 3]
[1, 2, 3]

有人可以解释一下为什么我得到这个输出?

1 个答案:

答案 0 :(得分:1)

我越深入这个兔子洞,这就越疯狂了。在玩了一下之后,我想我已经知道这里到底发生了什么,虽然我还不清楚其中的原因。

import xfrom x import y之间存在根本区别。在这两种情况下,文件x都将运行,其中的代码将被执行 - 包括任何print语句,这是有道理的。如果使用import x,则执行文件x,保留其命名空间(包括任何函数或变量定义),并且命名空间中包含指向模块x的链接。当前文件。如果您使用from x import y,则会执行文件x,然后将y副本粘贴到当前文件的命名空间中。您实际上是在那个时间拍摄two的快照;任何未来的更改two都不会反映在three

让我们去翻译:

>>> execfile("two.py")     # This lets us run the file and keep the namespace
[1, 2]
[1, 2]
>>> id(test)
47312858429416
>>> id(three.test)
47312858429128

在这种情况下,testtwo.py创建的变量。由于ID不同,我们可以看到three模块创建了test的副本,而不是引用实际对象。

现在我们可以遵循python two.py的程序逻辑。我们首先创建test = [1,2]。然后执行three.py。此时,我们有一行from two import test,它将自动运行two.py,它将执行print语句(生成[1,2])。这也将使test的{​​{1}}的本地副本可供使用。然后three模块继续并附加3,但只会将3附加到其three 的本地副本。然后我们回到test继续执行。当我们打印two.py时,我们会打印我们的本地副本,test从未修改过。{1}}这就是我们第二次获得three的方式。

所以这里变得棘手。

再次查看上面的[1,2]代码,并将其与下面的代码进行比较,而不是执行execfile,而是运行python two.py

import two

当我看到它时,我的大脑爆炸,原因有两个。首先:我们不再打印两次,只打印一次。第二:>>> import two [1, 2, 3] >>> two.test [1, 2, 3] >>> two.three.test [1, 2, 3] >>> id(two.test) 47787298457576 >>> id(two.three.test) 47787298457576 模块不再具有three的单独副本;它现在指的是与test完全相同的对象。

我得出的结论是如果导入two而不是将其作为主文件执行,Python将自动解析该循环引用,将two.py个对象链接在一起并阻止{多次执行{1}}。test尝试执行导入时,two检测到它将要再次导入,而不是再次执行该代码,它将会只需将three的副本提供给two即可。无论如何,这就是我如何理解它。

考虑到这一点是有道理的,因为否则你最终会得到一个无限的递归循环。我有点惊讶的是,Python并不会因为尝试这种圆形怪异而打击用户,因为它会捕获其他类似的导入问题,但这也是处理它的一种非常好的方法。 / p>

但这解释了test的不同行为。如果你看一下three会发生什么,我们就会看到" import"来自python one.py的行为,而不是"执行"行为。完成该行代码后,from two import test包含two对象,其中包含two,并且已打印一次。然后将其复制到test的命名空间中并再次打印。这就是我们如何得到这种奇怪的行为。

所以tl; dr:Python将以不同方式执行您的代码,具体取决于您是正常执行文件还是导入文件。我们运行[1,2,3]的不同方式完全不同。但是并不依赖Python以合理的方式解析循环引用;这感觉就像给我玩游戏系统一样,大多数时候会有更好的方法来设计你的程序而不必处理这种混乱。

有了这个,我午休了。