我有三个文件:

1)一个

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]


谁能解释我为什么得到这个输出?

最佳答案

我越深入这个兔子洞,就会越疯狂。经过一段时间的研究之后,我认为我对这里到底发生了什么有所了解,尽管我仍不清楚其中的一些原因。

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


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

现在我们可以遵循python two.py的程序逻辑。我们从创建test = [1,2]开始。然后执行three.py。此时,我们有一行from two import test,它将自动运行two.py,该行将执行print语句(导致[1,2])。这还将制作test的本地副本以供three使用。然后,three模块继续并追加3,但仅将3追加到其本地副本test。然后我们回到two.py继续执行。当我们打印test时,我们正在打印它的本地副本,而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完全相同。

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

考虑这一点很有意义,因为否则,您可能会遇到无限递归循环。我感到惊讶的是,Python不仅会给用户打圆形循环的怪味,因为它会捕获其他类似的导入问题,但这也是一种很好的处理方式。

但这解释了python one.py的不同行为。如果您查看from two import test会发生什么,看来我们看到的是two中的“导入”行为,而不是“执行”行为。完成该行代码后,two将包含一个包含test[1,2,3]对象,并已将其打印一次。然后将其复制到one的命名空间中并再次打印。这就是我们得到这种奇怪行为的方式。

tl; dr:Python将根据您是正常执行文件还是导入文件来不同地执行代码。我们运行two的不同方式使一切有所不同。但不要依赖Python以合理的方式解析循环引用;这对我来说就像在玩系统游戏,大多数时候,会有一种更好的方法来设计程序,而不必处理这种混乱。

因此,我正在午休。

关于python - 循环进口困惑,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/25064228/

10-13 09:32