直接导入__init__.py时其__name__属性为何不同,且顶层代码执行两次?
哈哈,这个问题我刚看到的时候也愣了一下,不过拆解Python的导入逻辑就懂了,我给你慢慢说~
首先得先搞懂Python识别模块的核心逻辑:Python是靠模块名来标记和缓存模块的,不是看文件路径。咱们一步步看你的代码发生了什么:
当你执行
import silly_test.foo时,Python会先去加载silly_test这个包——也就是执行它的__init__.py。这时候Python会把这个模块以silly_test为键,存在sys.modules缓存里,同时把这个文件的__name__设为包名silly_test,所以你会看到第一行输出Init name: silly_test。接下来你写了
import silly_test.__init__,这就有意思了:Python会把silly_test.__init__当成一个全新的、独立的模块名。它会找到对应的文件(还是那个__init__.py),但去sys.modules里查的时候,发现没有silly_test.__init__这个键,所以它会重新执行这个文件的顶层代码!这时候__name__就被设成了这个新的模块名silly_test.__init__,也就是你看到的第三行输出。
这就解释了你两个疑惑:
- 顶层代码执行两次:因为Python把这当成了两个不同的模块,两次加载同一个文件
__name__不同:每次加载时,Python会根据你使用的模块名来设置这个属性
至于你问的,还有哪些情况会出现“多个__name__对应同一个文件”的情况?其实只要能让Python用不同的模块名指向同一个物理文件就行,举几个常见的例子:
- 给同一个文件创建不同的符号链接(比如把
a.py链接成b.py),然后分别import a和import b,这两个模块名对应同一个文件,会被分别加载 - 把同一个文件所在的目录多次添加到
sys.path里,然后用不同的路径前缀导入它(不过这种情况比较少见,也不推荐) - 还有像你这种,把包的
__init__.py分别用包名和包名.__init__两种方式导入,本质也是不同模块名对应同一个文件
最后要提醒一句:这种操作其实是Python导入机制的“边缘情况”,正常写代码的时候绝对不推荐这么干,多次加载同一个模块很容易导致变量状态混乱、重复初始化等问题,咱们导入包直接import silly_test或者from silly_test import xxx就好啦~
备注:内容来源于stack exchange,提问作者sixtyfootersdude




