我有三个文件:
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]
有人可以解释一下为什么我得到这个输出?
答案 0 :(得分:1)
我越深入这个兔子洞,这就越疯狂了。在玩了一下之后,我想我已经知道这里到底发生了什么,虽然我还不清楚其中的原因。
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
的{{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以合理的方式解析循环引用;这感觉就像给我玩游戏系统一样,大多数时候会有更好的方法来设计你的程序而不必处理这种混乱。
有了这个,我午休了。