我有三个文件:
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 x
和from 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/